diff --git a/.captain/config.yaml b/.captain/config.yaml
deleted file mode 100644
index bd17814aa9f1..000000000000
--- a/.captain/config.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-test-suites:
- detectors:
- command: gotestsum --jsonfile tmp/go-test.json --raw-command -- go test -tags=detectors -timeout=15m -json -count=1 -vet=off ./pkg/detectors/...
- results:
- path: tmp/go-test.json
- output:
- print-summary: true
- ## No retries right now
- # retries:
- # attempts: 3
- # command: gotestsum --raw-command --jsonfile tmp/go-test.json -- go test -tags=detectors -timeout=15m -json -count=1 -vet=off {{ package }} -run '{{ run }}'
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 10d9fcbaa24c..000000000000
--- a/.gitattributes
+++ /dev/null
@@ -1,2 +0,0 @@
-*.go text eol=lf
-*.md text eol=lf
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index d68ec0efa4e9..000000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: ""
-labels: bug, needs triage
-assignees: trufflesecurity/product-eng
----
-
-Please review the [Community Note](https://github.com/trufflesecurity/trufflehog/blob/main/.github/community_note.md) before submitting
-
-### TruffleHog Version
-
-
-### Trace Output
-
-
-
-### Expected Behavior
-
-
-
-### Actual Behavior
-
-
-
-### Steps to Reproduce
-
-
- 1. Go to '...'
- 2. Click on '....'
- 3. Scroll down to '....'
- 4. See error
-
-## Environment
- * OS: [e.g. iOS]
- * Version [e.g. 22]
-
-## Additional Context
-
-
-### References
-
-
-
-* #0000
-
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 3a1f96dbd3ab..000000000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-title: ""
-labels: enhancement, needs triage
-assignees: trufflesecurity/product-eng
----
-
-Please review the [Community Note](https://github.com/trufflesecurity/trufflehog/blob/main/.github/community_note.md) before submitting
-
-## Description
-
-
-### Preferred Solution
-
-
-### Additional Context
-
-
-#### References
-
-
-
-
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
deleted file mode 100644
index 9725c0817d43..000000000000
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-### Description:
-Explain the purpose of the PR.
-
-### Checklist:
-* [ ] Tests passing (`make test-community`)?
-* [ ] Lint passing (`make lint` this requires [golangci-lint](https://golangci-lint.run/welcome/install/#local-installation))?
diff --git a/.github/community_note.md b/.github/community_note.md
deleted file mode 100644
index 77ae41dab5a6..000000000000
--- a/.github/community_note.md
+++ /dev/null
@@ -1,7 +0,0 @@
-## Community Note
-
-Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request.
-
-Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request.
-
-If you are interested in working on this issue or have submitted a pull request, please leave a comment.
diff --git a/.github/renovate.json b/.github/renovate.json
deleted file mode 100644
index 319817ef7ced..000000000000
--- a/.github/renovate.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "extends": [
- "config:base"
- ],
- "prConcurrentLimit": 3,
- "prHourlyLimit": 2
-}
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
deleted file mode 100644
index 6f1744f772f5..000000000000
--- a/.github/workflows/README.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# GitHub Workflows
-
-This directory contains GitHub Actions workflows for the TruffleHog repository.
-
-## PR Approval Check (`pr-approval-check.yml`)
-
-This workflow enforces that at least one PR approver must be an **active** member of the `@trufflesecurity/product-eng` team or any of its child teams.
-
-### How it works:
-
-1. **Triggers**: The workflow runs on:
- - `pull_request_review` events when a review is submitted (`submitted` type)
- - `pull_request` events when a PR is opened, reopened, or synchronized (`opened`, `reopened`, `synchronize` types)
-
-2. **Approval Check Process**: The workflow:
- - Fetches all reviews for the PR using the GitHub API
- - Filters for reviews with state `APPROVED`
- - Gets all child teams of `@trufflesecurity/product-eng` using `listChildInOrg` API
- - Checks if any approver is an **active** member (not pending) of either:
- - The parent `@trufflesecurity/product-eng` team, OR
- - Any of its child teams
- - Sets a commit status accordingly
-
-3. **Status Check**: Creates a commit status named `product-eng-approval` with:
- - ✅ **Success**: When at least one approver is an active member of `@trufflesecurity/product-eng` or any child team
- - ❌ **Failure**: When there are no approvals or there are approvals but none from active `@trufflesecurity/product-eng` members
-
-### Error Handling
-
-If there are errors listing reviews or checking team membership, the workflow reports a failure status and also fails itself.
-
-### Branch Protection
-
-To make this check required:
-
-1. Go to Settings → Branches
-2. Add or edit a branch protection rule for your main branch
-3. Enable "Require status checks to pass before merging"
-4. Add `pr-approval-check` to the required status checks
-
-### Permissions
-
-The workflow uses the default `GITHUB_TOKEN` which has sufficient permissions to:
-- Read PR reviews
-- List child teams and check team membership (for public teams)
-- Create commit statuses
-
-**Note**: If the `product-eng` team or its child teams are private, you may need to use a personal access token with appropriate permissions. The Github API returns 404 for non-members and for lack of permissions.
\ No newline at end of file
diff --git a/.github/workflows/TESTING.md b/.github/workflows/TESTING.md
deleted file mode 100644
index af66a4776e55..000000000000
--- a/.github/workflows/TESTING.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Testing
-
-Most testing is handled automatically by our GitHub Actions workflows.
-
-## Local GitHub Action Testing
-
-In some cases you may wish to submit changes to the Trufflehog GitHub Action. Unfortunately GitHub does not provide a 1st-party testing environment for testing actions outside of GitHub Actions.
-
-Fortunately [nektos/act](https://github.com/nektos/act) enables local testing of GitHub Actions.
-
-### Instructions
-
-1. Please follow [the installation instructions](http://https://github.com/nektos/act#installation) for your OS.
-2. The first run of `act` will ask you to specify an image. `Medium` should suffice.
-3. You'll need to configure a personal-access-token(PAT) with: `repo:status`, `repo_deployment`, and `public_repo` permissions.
-4. Set an environment variable named `GITHUB_TOKEN` with the PAT from the previous step as the value: `$ export GITHUB_TOKEN=`
-5. Run the following command from the repository root: `act pull_request -j test -W .github/workflows/secrets.yml -s GITHUB_TOKEN --defaultbranch main`
-6. If the job was successful, you should expect to see output from the scanner showing several detected secrets.
-7. If you want to omit the context of a pull request event and just test that the action starts successfully, run: `act -j test -W .github/workflows/secrets.yml -s GITHUB_TOKEN --defaultbranch main`
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
deleted file mode 100644
index 6292ade7d032..000000000000
--- a/.github/workflows/codeql-analysis.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-# supported CodeQL languages.
-#
-name: "CodeQL"
-
-on:
- push:
- branches: [main]
- pull_request:
- # The branches below must be a subset of the branches above
- branches: [main]
- schedule:
- - cron: "35 11 * * 2"
-
-jobs:
- analyze:
- name: Analyze
- runs-on: ubuntu-latest
- permissions:
- actions: read
- contents: read
- security-events: write
-
- strategy:
- fail-fast: false
- matrix:
- language: ["go"]
- # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
- # Learn more about CodeQL language support at https://git.io/codeql-language-support
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- - name: Install Go
- uses: actions/setup-go@v5
- with:
- go-version: "1.24"
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v3
- with:
- languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
- # queries: ./path/to/local/query, your-org/your-repo/queries@main
- - name: Smoke
- run: |
- go build .
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/detector-tests.yml b/.github/workflows/detector-tests.yml
deleted file mode 100644
index a777ccb6aa84..000000000000
--- a/.github/workflows/detector-tests.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-name: Detectors Aggregation
-
-on:
- workflow_dispatch:
- schedule:
- - cron: "0 8 * * *"
-
-jobs:
- test-detectors:
- if: ${{ github.repository == 'trufflesecurity/trufflehog' }}
- runs-on: ubuntu-latest
- permissions:
- actions: "read"
- contents: "read"
- id-token: "write"
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-go@v5
- - name: Install gotestsum
- uses: jaxxstorm/action-install-gh-release@v1.14.0
- with:
- repo: gotestyourself/gotestsum
- - uses: rwx-research/setup-captain@v1
- - name: Test Go
- run: |
- export CGO_ENABLED=1
- captain run detectors
- env:
- RWX_ACCESS_TOKEN: ${{ secrets.RWX_ACCESS_TOKEN }}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
deleted file mode 100644
index c6675888ffb2..000000000000
--- a/.github/workflows/lint.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-name: Lint
-
-on:
- push:
- tags:
- - v*
- pull_request:
-
-permissions:
- contents: read
- pull-requests: read
-
-jobs:
- golangci-lint:
- name: golangci-lint
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-go@v5
- with:
- go-version: "1.24"
- - name: golangci-lint
- uses: golangci/golangci-lint-action@v6
- with:
- # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
- version: latest
- # Optional: working directory, useful for monorepos
- # working-directory: somedir
-
- # Optional: golangci-lint command line arguments.
- args: --enable bodyclose --enable copyloopvar --enable misspell --timeout 10m
-
- # Optional: if set to true then the action don't cache or restore ~/go/pkg.
- # skip-pkg-cache: true
-
- # Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
- # skip-build-cache: true
- semgrep:
- name: semgrep
- runs-on: ubuntu-latest
- container:
- image: returntocorp/semgrep
- if: (github.actor != 'dependabot[bot]')
- steps:
- - uses: actions/checkout@v4
- - run: semgrep --config=hack/semgrep-rules/detectors.yaml pkg/detectors/
diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml
deleted file mode 100644
index b7d3f3f3f280..000000000000
--- a/.github/workflows/performance.yml
+++ /dev/null
@@ -1,96 +0,0 @@
-name: Performance Test
-
-on: [pull_request]
-
-jobs:
- speed:
- # skip if PR is from a fork.
- # TODO: this could probabaly be refactored a bit so that it runs on forks
- if: ${{ ! github.event.pull_request.head.repo.fork }}
-
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
- ref: ${{ github.head_ref }}
-
- - name: Install Go
- uses: actions/setup-go@v5
- with:
- go-version: "1.24"
-
- - name: Run Head
- run: |
- go build -o current .
- repo_tmp=$(mktemp -d)
- git clone https://github.com/trufflesecurity/trufflehog.git $repo_tmp
- cd $repo_tmp
- git checkout v3.75.1
-
- user_time_sum=0
-
- for i in {1..5}
- do
- tmpfile=$(mktemp)
- /usr/bin/time -o $tmpfile $GITHUB_WORKSPACE/current filesystem "$repo_tmp" --no-verification --no-update > out.txt
- cat $tmpfile
- time_output=$(cat $tmpfile)
- rm $tmpfile
- user_time=$(echo $time_output | awk '{print $1}' | sed 's/user//')
-
- # Add the user time to the sum
- user_time_sum=$(echo "$user_time_sum + $user_time" | bc)
- done
-
- average_user_time=$(echo "scale=3; $user_time_sum / 5" | bc)
- echo HEAD_TIME=$average_user_time >> $GITHUB_ENV
-
- - name: Figure out previous tag
- run: |
- git fetch --tags
- git tag -l --sort=-v:refname | head -n 1 > previous_tag.txt
- echo PREVIOUS_TAG=$(cat previous_tag.txt) >> $GITHUB_ENV
-
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
- ref: ${{ env.PREVIOUS_TAG }}
-
- - name: Run Previous
- run: |
- go build -o previous .
- repo_tmp=$(mktemp -d)
- git clone https://github.com/trufflesecurity/trufflehog.git $repo_tmp
- cd $repo_tmp
- git checkout v3.75.1
-
- user_time_sum=0
-
- for i in {1..5}
- do
- tmpfile=$(mktemp)
- /usr/bin/time -o $tmpfile $GITHUB_WORKSPACE/previous filesystem "$repo_tmp" --no-verification --no-update > out.txt
- cat $tmpfile
- time_output=$(cat $tmpfile)
- rm $tmpfile
- user_time=$(echo $time_output | awk '{print $1}' | sed 's/user//')
-
- # Add the user time to the sum
- user_time_sum=$(echo "$user_time_sum + $user_time" | bc)
- done
-
- average_user_time=$(echo "scale=3; $user_time_sum / 5" | bc)
- echo PREVIOUS_TIME=$average_user_time >> $GITHUB_ENV
-
- - name: Compare Results
- run: |
- echo "head ($GITHUB_SHA) avg time (n=5): $HEAD_TIME"
- echo "$PREVIOUS_TAG avg time (n=5): $PREVIOUS_TIME"
- if [ $(echo "$HEAD_TIME > $PREVIOUS_TIME * 1.5" | bc) -eq 1 ]
- then
- echo "HEAD run time is at least 10% slower than PREVIOUS run time"
- exit 1
- fi
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
deleted file mode 100644
index 1f9b65c0aeb4..000000000000
--- a/.github/workflows/release.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-name: Release
-
-on:
- push:
- tags:
- - v*
-
-permissions:
- contents: write
- packages: write
- id-token: write
-
-jobs:
- Release:
- runs-on: ubuntu-latest
- env:
- DOCKER_CLI_EXPERIMENTAL: "enabled"
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- - name: Docker Login to DockerHub
- uses: docker/login-action@v3
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
- - name: Docker Login to GitHub Container Registry
- uses: docker/login-action@v3
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Set up Go
- uses: actions/setup-go@v5
- with:
- go-version: "1.24"
- - name: Cosign install
- uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
- - name: Install UPX
- run: |
- sudo apt-get update
- sudo apt-get install -y upx
- - name: Run GoReleaser
- uses: goreleaser/goreleaser-action@v6
- with:
- distribution: goreleaser-pro
- version: latest
- args: release --clean
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
- GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
diff --git a/.github/workflows/secrets.yml b/.github/workflows/secrets.yml
deleted file mode 100644
index 0b0614a99cd1..000000000000
--- a/.github/workflows/secrets.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Scan for secrets
-
-on:
- push:
- tags:
- - v*
- branches:
- - main
- pull_request:
- workflow_dispatch:
-
-jobs:
- test:
- if: ${{ github.repository == 'trufflesecurity/trufflehog' && !github.event.pull_request.head.repo.fork }}
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
- ref: ${{ github.head_ref }}
- - name: Dogfood
- uses: ./
- id: dogfood
- with:
- extra_args: --results=verified
diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml
deleted file mode 100644
index c973fcf36edc..000000000000
--- a/.github/workflows/smoke.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-name: Smoke
-
-on:
- pull_request:
-
-jobs:
- smoke:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: Install Go
- uses: actions/setup-go@v5
- with:
- go-version: "1.24"
- - name: Smoke
- run: |
- set -e
- go run . git https://github.com/dustin-decker/secretsandstuff.git > /dev/null
- go run . github --repo https://github.com/dustin-decker/secretsandstuff.git > /dev/null
- zombies:
- runs-on: ubuntu-latest
- timeout-minutes: 5
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: Install Go
- uses: actions/setup-go@v5
- with:
- go-version: "1.24"
- - name: Run trufflehog
- run: |
- set -e
- go run . git --no-verification file://. > /dev/null
- # This case previously had a deadlock issue and left zombies after trufflehog exited #3379
- go run . git --no-verification https://github.com/git-test-fixtures/binary.git > /dev/null
- - name: Check for running git processes and zombies
- run: |
- if pgrep -x "git" > /dev/null
- then
- echo "Git processes are still running"
- exit 1
- else
- echo "No git processes found"
- fi
-
- if ps -A -ostat,ppid | grep -e '[zZ]' > /dev/null
- then
- echo "Zombie processes found"
- exit 1
- else
- echo "No zombie processes found"
- fi
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index c46415ddc247..000000000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,69 +0,0 @@
-name: Test
-
-on:
- push:
- tags:
- - v*
- branches:
- - main
- pull_request:
-
-jobs:
- test:
- if: ${{ github.repository == 'trufflesecurity/trufflehog' && !github.event.pull_request.head.repo.fork }}
- runs-on: ubuntu-latest
- permissions:
- actions: "read"
- contents: "read"
- id-token: "write"
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: Install Go
- uses: actions/setup-go@v5
- with:
- go-version: "1.24"
- - id: "auth"
- uses: "google-github-actions/auth@v2"
- with:
- workload_identity_provider: "projects/811013774421/locations/global/workloadIdentityPools/github-pool/providers/github-provider"
- service_account: "github-ci-external@trufflehog-testing.iam.gserviceaccount.com"
- - name: Set up gotestsum
- run: |
- go install gotest.tools/gotestsum@latest
- mkdir -p tmp/test-results
- - name: Test
- run: |
- CGO_ENABLED=1 gotestsum --junitfile tmp/test-results/test.xml --raw-command -- go test -json -tags=sources $(go list ./... | grep -v /vendor/ | grep -v pkg/analyzer/analyzers)
- if: ${{ success() || failure() }} # always run this step, even if there were previous errors
- - name: Upload test results to BuildPulse for flaky test detection
- if: ${{ !cancelled() }} # Run this step even when the tests fail. Skip if the workflow is cancelled.
- uses: buildpulse/buildpulse-action@main
- with:
- account: 79229934
- repository: 77726177
- path: |
- tmp/test-results/*.xml
- key: ${{ secrets.BUILDPULSE_ACCESS_KEY_ID }}
- secret: ${{ secrets.BUILDPULSE_SECRET_ACCESS_KEY }}
- tags: integration
- - name: Annotate test results
- uses: mikepenz/action-junit-report@v5
- if: success() || failure() # always run even if the previous step fails
- with:
- report_paths: "tmp/test-results/*.xml"
- test-community:
- if: ${{ github.event.pull_request.head.repo.fork }}
- runs-on: ubuntu-latest
- permissions:
- actions: "read"
- contents: "read"
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: Install Go
- uses: actions/setup-go@v5
- with:
- go-version: "1.24"
- - name: Test
- run: make test-community
diff --git a/.gitignore b/.gitignore
index 6abf4e766574..bb85dcc36137 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,5 @@
-.idea
-dist
-.env
-*.test
-
-# binary
-trufflehog
-tmp/go-test.json
-.captain/detectors/timings.yaml
-.captain/detectors/quarantines.yaml
-.captain/detectors/flakes.yaml
-.vscode
+/build/
+/dist/
+/truffleHog.egg-info/
+**/__pycache__/
+**/*.pyc
diff --git a/.goreleaser.yml b/.goreleaser.yml
deleted file mode 100644
index b7b354462758..000000000000
--- a/.goreleaser.yml
+++ /dev/null
@@ -1,124 +0,0 @@
-version: 2
-builds:
- - id: trufflehog-upx
- binary: trufflehog
- ldflags:
- - -s -w -X 'github.com/trufflesecurity/trufflehog/v3/pkg/version.BuildVersion={{ .Version }}'
- env: [CGO_ENABLED=0]
- goos:
- - linux
- goarch:
- - amd64
- - arm64
- hooks:
- post:
- - upx -q "{{ .Path }}"
- - id: trufflehog
- binary: trufflehog
- ldflags:
- - -X 'github.com/trufflesecurity/trufflehog/v3/pkg/version.BuildVersion={{ .Version }}'
- env: [CGO_ENABLED=0]
- goos:
- - darwin
- - windows
- goarch:
- - amd64
- - arm64
-dockers:
- - image_templates: ["trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64"]
- dockerfile: Dockerfile.goreleaser
- extra_files:
- - entrypoint.sh
- use: buildx
- build_flag_templates:
- - --platform=linux/amd64
- - --label=org.opencontainers.image.title={{ .ProjectName }}
- - --label=org.opencontainers.image.description={{ .ProjectName }}
- - --label=org.opencontainers.image.url=https://github.com/trufflesecurity/{{ .ProjectName }}
- - --label=org.opencontainers.image.source=https://github.com/trufflesecurity/{{ .ProjectName }}
- - --label=org.opencontainers.image.version={{ .Version }}
- - --label=org.opencontainers.image.revision={{ .FullCommit }}
- - --label=org.opencontainers.image.licenses=AGPL-3.0
- - image_templates: ["trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8"]
- goarch: arm64
- dockerfile: Dockerfile.goreleaser
- extra_files:
- - entrypoint.sh
- use: buildx
- build_flag_templates:
- - --platform=linux/arm64/v8
- - --label=org.opencontainers.image.title={{ .ProjectName }}
- - --label=org.opencontainers.image.description={{ .ProjectName }}
- - --label=org.opencontainers.image.url=https://github.com/trufflesecurity/{{ .ProjectName }}
- - --label=org.opencontainers.image.source=https://github.com/trufflesecurity/{{ .ProjectName }}
- - --label=org.opencontainers.image.version={{ .Version }}
- - --label=org.opencontainers.image.revision={{ .FullCommit }}
- - --label=org.opencontainers.image.licenses=AGPL-3.0
- - image_templates: ["ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64"]
- dockerfile: Dockerfile.goreleaser
- extra_files:
- - entrypoint.sh
- use: buildx
- build_flag_templates:
- - --platform=linux/amd64
- - --label=org.opencontainers.image.title={{ .ProjectName }}
- - --label=org.opencontainers.image.description={{ .ProjectName }}
- - --label=org.opencontainers.image.url=https://github.com/trufflesecurity/{{ .ProjectName }}
- - --label=org.opencontainers.image.source=https://github.com/trufflesecurity/{{ .ProjectName }}
- - --label=org.opencontainers.image.version={{ .Version }}
- - --label=org.opencontainers.image.revision={{ .FullCommit }}
- - --label=org.opencontainers.image.licenses=AGPL-3.0
- - image_templates: ["ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8"]
- goarch: arm64
- dockerfile: Dockerfile.goreleaser
- extra_files:
- - entrypoint.sh
- use: buildx
- build_flag_templates:
- - --platform=linux/arm64/v8
- - --label=org.opencontainers.image.title={{ .ProjectName }}
- - --label=org.opencontainers.image.description={{ .ProjectName }}
- - --label=org.opencontainers.image.url=https://github.com/trufflesecurity/{{ .ProjectName }}
- - --label=org.opencontainers.image.source=https://github.com/trufflesecurity/{{ .ProjectName }}
- - --label=org.opencontainers.image.version={{ .Version }}
- - --label=org.opencontainers.image.revision={{ .FullCommit }}
- - --label=org.opencontainers.image.licenses=AGPL-3.0
-docker_manifests:
- - name_template: trufflesecurity/{{ .ProjectName }}:{{ .Version }}
- image_templates:
- - trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64
- - trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8
- - name_template: trufflesecurity/{{ .ProjectName }}:latest
- image_templates:
- - trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64
- - trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8
- - name_template: ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}
- image_templates:
- - ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64
- - ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8
- - name_template: ghcr.io/trufflesecurity/{{ .ProjectName }}:latest
- image_templates:
- - ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64
- - ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8
-brews:
- - repository:
- owner: trufflesecurity
- name: homebrew-trufflehog
- token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
- description: "Find credentials all over the place"
- name: "trufflehog"
- homepage: "https://github.com/trufflesecurity/trufflehog"
- install: |
- bin.install "trufflehog"
-signs:
- - cmd: cosign
- signature: "${artifact}.sig"
- certificate: "${artifact}.pem"
- args:
- - "sign-blob"
- - "--oidc-issuer=https://token.actions.githubusercontent.com"
- - "--output-certificate=${certificate}"
- - "--output-signature=${signature}"
- - "${artifact}"
- - "--yes"
- artifacts: checksum
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
deleted file mode 100644
index ba7cefd3b07b..000000000000
--- a/.pre-commit-config.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-repos:
- - repo: https://github.com/rhysd/actionlint
- rev: v1.6.24
- hooks:
- - id: actionlint
-
- - repo: https://github.com/mpalmer/action-validator
- rev: v0.5.1
- hooks:
- - id: action-validator
diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml
deleted file mode 100644
index 1a19e6e7a155..000000000000
--- a/.pre-commit-hooks.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-- id: trufflehog
- name: TruffleHog
- description: Detect secrets in your data with TruffleHog.
- entry: trufflehog git file://. --since-commit HEAD --results=verified --fail
- language: golang
- pass_filenames: false
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000000..bd8dfc5cadd0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: python
+python:
+ - "2.7"
+script: pytest --cov=./ && codecov
diff --git a/CODEOWNERS b/CODEOWNERS
deleted file mode 100644
index bc69d48ed7a0..000000000000
--- a/CODEOWNERS
+++ /dev/null
@@ -1,55 +0,0 @@
-# catch-all
-* @trufflesecurity/product-eng
-
-# Scanning
-pkg/sources/ @trufflesecurity/Scanning
-pkg/writers/ @trufflesecurity/Scanning
-
-# Integrations
-pkg/sources/circleci/ @trufflesecurity/Integrations
-pkg/sources/docker/ @trufflesecurity/Integrations
-pkg/sources/elasticsearch/ @trufflesecurity/Integrations
-pkg/sources/filesystem/ @trufflesecurity/Integrations
-pkg/sources/gcs/ @trufflesecurity/Integrations
-pkg/sources/git/ @trufflesecurity/Integrations
-pkg/sources/github/ @trufflesecurity/Integrations
-pkg/sources/gitlab/ @trufflesecurity/Integrations
-pkg/sources/jenkins/ @trufflesecurity/Integrations
-pkg/sources/postman/ @trufflesecurity/Integrations
-pkg/sources/s3/ @trufflesecurity/Integrations
-pkg/sources/travisci/ @trufflesecurity/Integrations
-
-# Shared
-pkg/decoders/ @trufflesecurity/Scanning @trufflesecurity/OSS
-pkg/engine/ @trufflesecurity/Scanning @trufflesecurity/OSS
-pkg/gitparse/ @trufflesecurity/Scanning @trufflesecurity/OSS
-pkg/giturl/ @trufflesecurity/Scanning @trufflesecurity/OSS
-pkg/handlers/ @trufflesecurity/Scanning @trufflesecurity/OSS
-pkg/iobuf/ @trufflesecurity/Scanning @trufflesecurity/OSS
-pkg/sanitizer/ @trufflesecurity/Scanning @trufflesecurity/OSS
-proto/ @trufflesecurity/Scanning @trufflesecurity/Integrations
-
-# OSS
-pkg/detectors/ @trufflesecurity/OSS
-pkg/common/ @trufflesecurity/OSS
-pkg/custom_detectors/ @trufflesecurity/OSS
-pkg/analzyers/ @trufflesecurity/OSS
-pkg/engine/defaults/defaults.go @trufflesecurity/OSS
-pkg/engine/defaults/defaults_test.go @trufflesecurity/OSS
-
-# critical detectors
-pkg/detectors/aws/ @trufflesecurity/backend
-pkg/detectors/gcp/ @trufflesecurity/backend
-pkg/detectors/azure/ @trufflesecurity/backend
-pkg/detectors/okta/ @trufflesecurity/backend
-pkg/detectors/privatekey/ @trufflesecurity/backend
-pkg/detectors/slack/ @trufflesecurity/backend
-pkg/detectors/slackwebhook/ @trufflesecurity/backend
-pkg/detectors/microsoftteamswebhook/ @trufflesecurity/backend
-pkg/detectors/twilio/ @trufflesecurity/backend
-pkg/detectors/sendgrid/ @trufflesecurity/backend
-pkg/detectors/gitlab/ @trufflesecurity/backend
-pkg/detectors/gitlabv2/ @trufflesecurity/backend
-pkg/detectors/github/ @trufflesecurity/backend
-pkg/detectors/github_old/ @trufflesecurity/backend
-pkg/detectors/githubapp/ @trufflesecurity/backend
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
deleted file mode 100644
index 2a603b1faba0..000000000000
--- a/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Contributor Covenant Code of Conduct
-
-## Our Pledge
-
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, sex characteristics, gender identity and expression,
-level of experience, education, socio-economic status, nationality, personal
-appearance, race, religion, or sexual identity and orientation.
-
-## Our Standards
-
-Examples of behavior that contributes to creating a positive environment
-include:
-
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-## Our Responsibilities
-
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
-
-Project maintainers have the right and responsibility to remove, edit, or
-reject comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct, or to ban temporarily or
-permanently any contributor for other behaviors that they deem inappropriate,
-threatening, offensive, or harmful.
-
-## Scope
-
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by project maintainers.
-
-## Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported by contacting the project team at community@trufflesec.com. All
-complaints will be reviewed and investigated and will result in a response that
-is deemed necessary and appropriate to the circumstances. The project team is
-obligated to maintain confidentiality with regard to the reporter of an incident.
-Further details of specific enforcement policies may be posted separately.
-
-Project maintainers who do not follow or enforce the Code of Conduct in good
-faith may face temporary or permanent repercussions as determined by other
-members of the project's leadership.
-
-## Attribution
-
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
-available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
-
-[homepage]: https://www.contributor-covenant.org
-
-For answers to common questions about this code of conduct, see
-https://www.contributor-covenant.org/faq
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 2badad299cd8..000000000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# Contribution guidelines
-
-Please create an issue to collect feedback prior to feature additions. If possible try to keep PRs scoped to one feature, and add tests for new features. We use the fork-based contribution model described by [GitHub's documentation](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project). (In short: Fork the TruffleHog repo and open a PR back from your fork into our default branch.)
-
-When showing interest in a bug, enhancement, PR, or issue, please use the thumbs up/thumbs down emoji on the original message rather than adding comments expressing the same.
-
-Contributors need to [sign our CLA](https://cla-assistant.io/trufflesecurity/trufflehog) before we are able to accept contributions.
-
-# Resources
-
-## How things work
-
-It can be a bit daunting diving into the code and wrapping your head around the project from a high level. The following two docs help give that high level overview:
-* [Process Flow](docs/process_flow.md)
-* [Concurrency Overview](docs/concurrency.md)
-
-## Adding new secret detectors
-
-We have published some [documentation and tooling to get started on adding new secret detectors](hack/docs/Adding_Detectors_external.md). Let's improve detection together!
-
-## Logging in TruffleHog
-
-**Use fields over format strings**. For structured logging, fields allow us to better filter and search through logs than embedding data in the message.
-
-**Differentiate logs coming from dependencies**. This can be done with a `"dep"` field that gets passed to the library. Sometimes it’s not possible to do this.
-
-Limit log levels to _**info**_ (indicate normal or expected operation) and _**error**_ (functionality is impeded and should be checked by an engineer)
-
-**Choose an appropriate verbosity level**
-```
-0. — logs we always want to see
-1. — logs we could possibly want to turn off
-2. — logs that are useful for debugging
-3. — frequently called logs that may produce a lot of output
-4. — extremely verbose logs or logs containing sensitive information
-5. — ultimate verbosity
-```
-Example: `Logger().V(2).Info("skipping file: extension is ignored", "ext", mimeExt)`
-
-**Either log an error or return it**. Doing one or the other will help defer logging for when there is more context for it and prevent duplicate “bubbling up” logs.
-
-**Log contextual information**. Every log emitted should contain this context via fields to easily filter and search.
diff --git a/Dockerfile b/Dockerfile
index 88f948949b69..b0168a82201d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,17 +1,7 @@
-FROM --platform=${BUILDPLATFORM} golang:bullseye as builder
-
-WORKDIR /build
-COPY . .
-ENV CGO_ENABLED=0
-ARG TARGETOS TARGETARCH
-RUN --mount=type=cache,target=/go/pkg/mod \
- --mount=type=cache,target=/root/.cache/go-build \
- GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o trufflehog .
-
-FROM alpine:3.22
-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
-COPY entrypoint.sh /etc/entrypoint.sh
-RUN chmod +x /etc/entrypoint.sh
-ENTRYPOINT ["/etc/entrypoint.sh"]
+FROM python:3-alpine
+RUN apk add --no-cache git && pip install trufflehog
+RUN adduser -S truffleHog
+USER truffleHog
+WORKDIR /proj
+ENTRYPOINT [ "trufflehog" ]
+CMD [ "-h" ]
diff --git a/Dockerfile.goreleaser b/Dockerfile.goreleaser
deleted file mode 100644
index c7da19f6a664..000000000000
--- a/Dockerfile.goreleaser
+++ /dev/null
@@ -1,9 +0,0 @@
-FROM alpine:3.22
-
-RUN apk add --no-cache bash git openssh-client ca-certificates \
- && rm -rf /var/cache/apk/* && update-ca-certificates
-WORKDIR /usr/bin/
-COPY trufflehog .
-COPY entrypoint.sh /etc/entrypoint.sh
-RUN chmod +x /etc/entrypoint.sh
-ENTRYPOINT ["/etc/entrypoint.sh"]
diff --git a/LICENSE b/LICENSE
index d2bc34f9900a..23cb790338e1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,620 +1,281 @@
- GNU AFFERO GENERAL PUBLIC LICENSE
- Version 3, 19 November 2007
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
- Copyright (C) 2007 Free Software Foundation, Inc.
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
- The GNU Affero General Public License is a free, copyleft license for
-software and other kinds of works, specifically designed to ensure
-cooperation with the community in the case of network server software.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-our General Public Licenses are intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users.
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- Developers that use our General Public Licenses protect your rights
-with two steps: (1) assert copyright on the software, and (2) offer
-you this License which gives you legal permission to copy, distribute
-and/or modify the software.
-
- A secondary benefit of defending all users' freedom is that
-improvements made in alternate versions of the program, if they
-receive widespread use, become available for other developers to
-incorporate. Many developers of free software are heartened and
-encouraged by the resulting cooperation. However, in the case of
-software used on network servers, this result may fail to come about.
-The GNU General Public License permits making a modified version and
-letting the public access it on a server without ever releasing its
-source code to the public.
-
- The GNU Affero General Public License is designed specifically to
-ensure that, in such cases, the modified source code becomes available
-to the community. It requires the operator of a network server to
-provide the source code of the modified version running there to the
-users of that server. Therefore, public use of a modified version, on
-a publicly accessible server, gives the public access to the source
-code of the modified version.
-
- An older license, called the Affero General Public License and
-published by Affero, was designed to accomplish similar goals. This is
-a different license, not a version of the Affero GPL, but Affero has
-released a new version of the Affero GPL which permits relicensing under
-this license.
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU Affero General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
this License.
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Remote Network Interaction; Use with the GNU General Public License.
-
- Notwithstanding any other provision of this License, if you modify the
-Program, your modified version must prominently offer all users
-interacting with it remotely through a computer network (if your version
-supports such interaction) an opportunity to receive the Corresponding
-Source of your version by providing access to the Corresponding Source
-from a network server at no charge, through some standard or customary
-means of facilitating copying of software. This Corresponding Source
-shall include the Corresponding Source for any work covered by version 3
-of the GNU General Public License that is incorporated pursuant to the
-following paragraph.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the work with which it is combined will remain governed by version
-3 of the GNU General Public License.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU Affero General Public License from time to time. Such new versions
-will be similar in spirit to the present version, but may differ in detail to
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU Affero General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU Affero General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU Affero General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
@@ -626,50 +287,53 @@ free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
+convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
-
- Copyright (C)
+ {description}
+ Copyright (C) {year} {fullname}
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License, or
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
+ GNU General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
- If your software can interact with users remotely through a computer
-network, you should also make sure that it provides a way for users to
-get its source. For example, if your program is a web application, its
-interface could display a "Source" link that leads users to an archive
-of the code. There are many ways you could offer source, and different
-solutions will be better for different programs; see section 13 for the
-specific requirements.
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU AGPL, see
- .
-Copy license text to clipboard
-Suggest this license
-Make a pull request to suggest this license for a project that is not licensed. Please be polite: see if a license has already been suggested, try to suggest a license fitting for the project’s community, and keep your communication with project maintainers friendly.
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
-Enter GitHub repository URL
-How to apply this license
-Create a text file (typically named LICENSE or LICENSE.txt) in the root of your source code and copy the text of the license into the file.
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
-Optional steps
-The Free Software Foundation recommends taking the additional step of adding a boilerplate notice to the top of each file. The boilerplate can be found at the end of the license.
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
-Add AGPL-3.0-or-later (or AGPL-3.0-only to disallow future versions) to your project’s package description, if applicable (e.g., Node.js, Ruby, and Rust). This will ensure the license is displayed in package directories.
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
- Source
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/Makefile b/Makefile
deleted file mode 100644
index b0c875900bb1..000000000000
--- a/Makefile
+++ /dev/null
@@ -1,65 +0,0 @@
-PROTOS_IMAGE ?= trufflesecurity/protos:1.22
-
-.PHONY: check
-.PHONY: lint
-.PHONY: test
-.PHONY: test-race
-.PHONY: run
-.PHONY: install
-.PHONY: protos
-.PHONY: protos-windows
-.PHONY: vendor
-.PHONY: dogfood
-
-dogfood:
- CGO_ENABLED=0 go run . git file://. --json --log-level=2
-
-install:
- CGO_ENABLED=0 go install .
-
-check:
- go fmt $(shell go list ./... | grep -v /vendor/)
- go vet $(shell go list ./... | grep -v /vendor/)
-
-lint:
- golangci-lint run --enable bodyclose --enable copyloopvar --enable misspell --out-format=colored-line-number --timeout 10m
-
-test-failing:
- CGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/) | grep FAIL
-
-test:
- CGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/)
-
-test-integration:
- CGO_ENABLED=0 go test -timeout=5m -tags=integration $(shell go list ./... | grep -v /vendor/)
-
-test-race:
- CGO_ENABLED=1 go test -timeout=5m -race $(shell go list ./... | grep -v /vendor/)
-
-test-detectors:
- CGO_ENABLED=0 go test -tags=detectors -timeout=5m $(shell go list ./... | grep pkg/detectors)
-
-test-community:
- CGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/ | grep -v pkg/sources | grep -v pkg/analyzer/analyzers)
-
-bench:
- CGO_ENABLED=0 go test $(shell go list ./pkg/secrets/... | grep -v /vendor/) -benchmem -run=xxx -bench .
-
-run:
- CGO_ENABLED=0 go run . git file://. --json
-
-run-debug:
- CGO_ENABLED=0 go run . git file://. --json --log-level=2
-
-protos:
- docker run --rm -u "$(shell id -u)" -v "$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))":/pwd "${PROTOS_IMAGE}" bash -c "cd /pwd; /pwd/scripts/gen_proto.sh"
-
-protos-windows:
- docker run --rm -v "$(shell cygpath -w $(shell pwd))":/pwd "${PROTOS_IMAGE}" bash -c "cd /pwd; ./scripts/gen_proto.sh"
-
-release-protos-image:
- docker buildx build --push --platform=linux/amd64,linux/arm64 \
- -t ${PROTOS_IMAGE} -f hack/Dockerfile.protos .
-
-test-release:
- goreleaser release --clean --skip-publish --snapshot
diff --git a/PreCommit.md b/PreCommit.md
deleted file mode 100644
index 6e66097cc900..000000000000
--- a/PreCommit.md
+++ /dev/null
@@ -1,210 +0,0 @@
-# TruffleHog Pre-Commit Hooks
-
-Pre-commit hooks are scripts that run automatically before a commit is completed, allowing you to check your code for issues before sharing it with others. TruffleHog can be integrated as a pre-commit hook to prevent credentials from leaking before they ever leave your computer.
-
-This guide covers how to set up TruffleHog as a pre-commit hook using two popular frameworks:
-
-1. [Git's hooksPath feature](#global-setup-using-gits-hookspath-feature) - A built-in Git feature for managing hooks globally
-2. [Using Pre-commit framework](#using-the-pre-commit-framework) - A language-agnostic framework for managing pre-commit hooks
-3. [Using Husky](#using-husky) - A Git hooks manager for JavaScript/Node.js projects
-
-## Prerequisites
-
-All of the methods require TruffleHog to be installed.
-
-1. Install TruffleHog:
-
-```bash
-# Using Homebrew (macOS)
-brew install trufflehog
-
-# Using installation script for Linux, macOS, and Windows (and WSL)
-curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin
-```
-
-## Global setup using Git's hooksPath feature
-
-This approach uses Git's `core.hooksPath` to apply hooks to all repositories without requiring any per-repository setup:
-
-1. Create a global hooks directory:
-
-```bash
-mkdir -p ~/.git-hooks
-```
-
-2. Create a pre-commit hook file:
-
-```bash
-touch ~/.git-hooks/pre-commit
-chmod +x ~/.git-hooks/pre-commit
-```
-
-3. Add the following content to `~/.git-hooks/pre-commit`:
-
-```bash
-#!/bin/sh
-
-trufflehog git file://. --since-commit HEAD --results=verified,unknown --fail
-```
-
-If you are using Docker, use this instead:
-
-```bash
-#!/bin/sh
-
-docker run --rm -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --results=verified,unknown --fail
-```
-
-4. Configure Git to use this hooks directory globally:
-
-```bash
-git config --global core.hooksPath ~/.git-hooks
-```
-
-Now all your repositories will automatically use this pre-commit hook without any additional setup.
-
-## Using the Pre-commit Framework
-
-The [pre-commit framework](https://pre-commit.com) is a powerful, language-agnostic tool for managing Git hooks.
-
-### Installation of Pre-commit
-
-1. Install the pre-commit framework:
-
-```bash
-# Using pip (Python)
-pip install pre-commit
-
-# Using Homebrew (macOS)
-brew install pre-commit
-
-# Using conda
-conda install -c conda-forge pre-commit
-```
-
-### Repository-Specific Setup
-
-To set up TruffleHog as a pre-commit hook for a specific repository:
-
-1. Create a `.pre-commit-config.yaml` file in the root of your repository:
-
-```yaml
-repos:
- - repo: local
- hooks:
- - id: trufflehog
- name: TruffleHog
- description: Detect secrets in your data.
- entry: bash -c 'trufflehog git file://. --since-commit HEAD --results=verified,unknown --fail'
- language: system
- stages: ["pre-commit", "pre-push"]
-```
-
-2. Install the pre-commit hook:
-
-```bash
-pre-commit install
-```
-
-## Using Husky
-
-[Husky](https://typicode.github.io/husky/) is a popular tool for managing Git hooks in JavaScript/Node.js projects.
-
-### Installation of Husky
-
-1. Install Husky in your project:
-
-```bash
-# npm
-npm install husky --save-dev
-
-# yarn
-yarn add husky --dev
-```
-
-2. Enable Git hooks:
-
-```bash
-# npm
-npx husky init
-```
-
-### Setting Up TruffleHog with Husky
-
-1. Add the following content to `.husky/pre-commit`:
-
-```bash
-echo "trufflehog git file://. --since-commit HEAD --results=verified,unknown --fail" > .husky/pre-commit
-```
-
-3. For Docker users, use this content instead:
-
-```bash
-echo 'docker run --rm -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --results=verified,unknown --fail' > .husky/pre-commit
-```
-
-## Best Practices
-
-### Commit Process
-
-For optimal hook efficacy:
-
-1. Execute `git add` followed by `git commit` separately. This ensures TruffleHog analyzes all intended changes.
-2. Avoid using `git commit -am`, as it might bypass pre-commit hook execution for unstaged modifications.
-
-### Skipping Hooks
-
-In rare cases, you may need to bypass pre-commit hooks:
-
-```bash
-git commit --no-verify -m "Your commit message"
-```
-
-### Running in Audit Mode
-
-You can run the TruffleHog pre-commit hook in an "audit" or "non-enforcement" mode to test the git hook with the following commands:
-
-Local Binary Version:
-```bash
-trufflehog git file://. --since-commit HEAD --results=verified,unknown 2>/dev/null
-```
-
-Docker Container Version:
-```bash
-docker run --rm -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --results=verified,unknown 2>/dev/null
-```
-
-This change does two things: (1) removes the `--fail` flag, which means the pre-commit hook will *always* pass, (2) suppresses `stderr` output, so only verified secrets are printed to the terminal output.
-
-**For users of the Pre-Commit Framework: add the `verbose: true` flag during audit mode; otherwise, the hook will pass, and you won't see any secrets.**
-
-## Troubleshooting
-
-### Hook Not Running
-
-If your pre-commit hook isn't running:
-
-1. Ensure the hook is executable:
-
- ```bash
- chmod +x .git/hooks/pre-commit
- ```
-
-2. Check if hooks are enabled:
-
- ```bash
- git config --get core.hooksPath
- ```
-
-### False Positives
-
-If you're getting false positives:
-
-1. Use the `--results=verified` flag to only show verified secrets
-2. Add `trufflehog:ignore` comments on lines with known false positives or risk-accepted findings
-
-## Conclusion
-
-By integrating TruffleHog into your pre-commit workflow, you can prevent credential leaks before they happen. Choose the setup method that best fits your project's needs and development workflow.
-
-For more information on TruffleHog's capabilities, refer to the [main documentation](README.md).
diff --git a/README.md b/README.md
index e8df00c28e7b..b3dabb855df6 100644
--- a/README.md
+++ b/README.md
@@ -1,737 +1,118 @@
-
-
-
TruffleHog
- Find leaked credentials.
-
+# truffleHog
+[](https://travis-ci.org/dxa4481/truffleHog)
+[](https://codecov.io/gh/dxa4481/truffleHog)
----
+Searches through git repositories for secrets, digging deep into commit history and branches. This is effective at finding secrets accidentally committed.
-
+## NEW
+truffleHog previously functioned by running entropy checks on git diffs. This functionality still exists, but high signal regex checks have been added, and the ability to surpress entropy checking has also been added.
-[](https://goreportcard.com/report/github.com/trufflesecurity/trufflehog/v3)
-[](/LICENSE)
-[](/pkg/detectors)
-
-
-
----
-
-# :mag_right: _Now Scanning_
-
-
-
-
-
-**...and more**
-
-To learn more about TruffleHog and its features and capabilities, visit our [product page](https://trufflesecurity.com/trufflehog?gclid=CjwKCAjwouexBhAuEiwAtW_Zx5IW87JNj97Ci7heFnA5ar6-DuNzT2Y5nIl9DuZ-FOUqx0Qg3vb9nxoClcEQAvD_BwE).
-
-
-
-# :globe_with_meridians: TruffleHog Enterprise
-
-Are you interested in continuously monitoring **Git, Jira, Slack, Confluence, Microsoft Teams, Sharepoint (and more)** for credentials? We have an enterprise product that can help! Learn more at .
-
-We take the revenue from the enterprise product to fund more awesome open source projects that the whole community can benefit from.
-
-
-
-# What is TruffleHog 🐽
-
-TruffleHog is the most powerful secrets **Discovery, Classification, Validation,** and **Analysis** tool. In this context, secret refers to a credential a machine uses to authenticate itself to another machine. This includes API keys, database passwords, private encryption keys, and more.
-
-## Discovery 🔍
-
-TruffleHog can look for secrets in many places including Git, chats, wikis, logs, API testing platforms, object stores, filesystems and more.
-
-## Classification 📁
-
-TruffleHog classifies over 800 secret types, mapping them back to the specific identity they belong to. Is it an AWS secret? Stripe secret? Cloudflare secret? Postgres password? SSL Private key? Sometimes it's hard to tell looking at it, so TruffleHog classifies everything it finds.
-
-## Validation ✅
-
-For every secret TruffleHog can classify, it can also log in to confirm if that secret is live or not. This step is critical to know if there’s an active present danger or not.
-
-## Analysis 🔬
-
-For the 20 some of the most commonly leaked out credential types, instead of sending one request to check if the secret can log in, TruffleHog can send many requests to learn everything there is to know about the secret. Who created it? What resources can it access? What permissions does it have on those resources?
-
-# :loudspeaker: Join Our Community
-
-Have questions? Feedback? Jump into Slack or Discord and hang out with us.
-
-Join our [Slack Community](https://join.slack.com/t/trufflehog-community/shared_invite/zt-pw2qbi43-Aa86hkiimstfdKH9UCpPzQ)
-
-Join the [Secret Scanning Discord](https://discord.gg/8Hzbrnkr7E)
-
-# :tv: Demo
-
-
-
-```bash
-docker run --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --org=trufflesecurity
-```
-
-# :floppy_disk: Installation
-
-Several options are available for you:
-
-### MacOS users
-
-```bash
-brew install trufflehog
-```
-
-### Docker:
-
-_Ensure Docker engine is running before executing the following commands:_
-
-#### Unix
-
-```bash
-docker run --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys
-```
-
-#### Windows Command Prompt
-
-```bash
-docker run --rm -it -v "%cd:/=\%:/pwd" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys
-```
-
-#### Windows PowerShell
-
-```bash
-docker run --rm -it -v "${PWD}:/pwd" trufflesecurity/trufflehog github --repo https://github.com/trufflesecurity/test_keys
-```
-
-#### M1 and M2 Mac
-
-```bash
-docker run --platform linux/arm64 --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys
-```
-
-### Binary releases
-
-```bash
-Download and unpack from https://github.com/trufflesecurity/trufflehog/releases
-```
-
-### Compile from source
-
-```bash
-git clone https://github.com/trufflesecurity/trufflehog.git
-cd trufflehog; go install
-```
-
-### Using installation script
-
-```bash
-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)
-
-```bash
-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
-
-```bash
-curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin
-```
-
-# :closed_lock_with_key: Verifying the artifacts
-
-Checksums are applied to all artifacts, and the resulting checksum file is signed using cosign.
-
-You need the following tool to verify signature:
-
-- [Cosign](https://docs.sigstore.dev/cosign/system_config/installation/)
-
-Verification steps are as follows:
-
-1. Download the artifact files you want, and the following files from the [releases](https://github.com/trufflesecurity/trufflehog/releases) page.
-
- - trufflehog\_{version}\_checksums.txt
- - trufflehog\_{version}\_checksums.txt.pem
- - trufflehog\_{version}\_checksums.txt.sig
-
-2. Verify the signature:
-
- ```shell
- cosign verify-blob \
- --certificate \
- --signature \
- --certificate-identity-regexp 'https://github\.com/trufflesecurity/trufflehog/\.github/workflows/.+' \
- --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
- ```
-
-3. Once the signature is confirmed as valid, you can proceed to validate that the SHA256 sums align with the downloaded artifact:
-
- ```shell
- sha256sum --ignore-missing -c trufflehog_{version}_checksums.txt
- ```
-
-Replace `{version}` with the downloaded files version
-
-Alternatively, if you are using the installation script, pass `-v` option to perform signature verification.
-This requires Cosign binary to be installed prior to running the installation script.
-
-# :rocket: Quick Start
-
-## 1: Scan a repo for only verified secrets
-
-Command:
-
-```bash
-trufflehog git https://github.com/trufflesecurity/test_keys --results=verified
-```
-
-Expected output:
-
-```
-🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷
-
-Found verified result 🐷🔑
-Detector Type: AWS
-Decoder Type: PLAIN
-Raw result: AKIAYVP4CIPPERUVIFXG
-Line: 4
-Commit: fbc14303ffbf8fb1c2c1914e8dda7d0121633aca
-File: keys
-Email: counter
-Repository: https://github.com/trufflesecurity/test_keys
-Timestamp: 2022-06-16 10:17:40 -0700 PDT
-...
-```
-
-## 2: Scan a GitHub Org for only verified secrets
-
-```bash
-trufflehog github --org=trufflesecurity --results=verified
-```
-
-## 3: Scan a GitHub Repo for only verified secrets and get JSON output
-
-Command:
-
-```bash
-trufflehog git https://github.com/trufflesecurity/test_keys --results=verified --json
-```
-
-Expected output:
-
-```
-{"SourceMetadata":{"Data":{"Git":{"commit":"fbc14303ffbf8fb1c2c1914e8dda7d0121633aca","file":"keys","email":"counter \u003ccounter@counters-MacBook-Air.local\u003e","repository":"https://github.com/trufflesecurity/test_keys","timestamp":"2022-06-16 10:17:40 -0700 PDT","line":4}}},"SourceID":0,"SourceType":16,"SourceName":"trufflehog - git","DetectorType":2,"DetectorName":"AWS","DecoderName":"PLAIN","Verified":true,"Raw":"AKIAYVP4CIPPERUVIFXG","Redacted":"AKIAYVP4CIPPERUVIFXG","ExtraData":{"account":"595918472158","arn":"arn:aws:iam::595918472158:user/canarytokens.com@@mirux23ppyky6hx3l6vclmhnj","user_id":"AIDAYVP4CIPPJ5M54LRCY"},"StructuredData":null}
-...
-```
-
-## 4: Scan a GitHub Repo + its Issues and Pull Requests
-
-```bash
-trufflehog github --repo=https://github.com/trufflesecurity/test_keys --issue-comments --pr-comments
-```
-
-## 5: Scan an S3 bucket for high-confidence results (verified + unknown)
-
-```bash
-trufflehog s3 --bucket= --results=verified,unknown
-```
-
-## 6: Scan S3 buckets using IAM Roles
-
-```bash
-trufflehog s3 --role-arn=
-```
-
-## 7: Scan a Github Repo using SSH authentication in Docker
-
-```bash
-docker run --rm -v "$HOME/.ssh:/root/.ssh:ro" trufflesecurity/trufflehog:latest git ssh://github.com/trufflesecurity/test_keys
-```
-
-## 8: Scan individual files or directories
-
-```bash
-trufflehog filesystem path/to/file1.txt path/to/file2.txt path/to/dir
-```
-
-## 9: Scan a local git repo
-
-Clone the git repo. For example [test keys](git@github.com:trufflesecurity/test_keys.git) repo.
-```bash
-$ git clone git@github.com:trufflesecurity/test_keys.git
-```
-
-Run trufflehog from the parent directory (outside the git repo).
-```bash
-$ trufflehog git file://test_keys --results=verified,unknown
-```
-
-To guard against malicious git configs in local scanning (see CVE-2025-41390), TruffleHog clones local git repositories to a temporary directory prior to scanning. This follows [Git's security best practices](https://git-scm.com/docs/git#_security). If you want to specify a custom path to clone the repository to (instead of tmp), you can use the `--clone-path` flag. If you'd like to skip the local cloning process and scan the repository directly (only do this for trusted repos), you can use the `--trust-local-git-config` flag.
-
-## 10: Scan GCS buckets for only verified secrets
-
-```bash
-trufflehog gcs --project-id= --cloud-environment --results=verified
-```
-
-## 11: Scan a Docker image for only verified secrets
-
-Use the `--image` flag multiple times to scan multiple images.
-
-```bash
-# to scan from a remote registry
-trufflehog docker --image trufflesecurity/secrets --results=verified
-
-# to scan from the local docker daemon
-trufflehog docker --image docker://new_image:tag --results=verified
-
-# to scan from an image saved as a tarball
-trufflehog docker --image file://path_to_image.tar --results=verified
-```
-
-## 12: Scan in CI
-
-Set the `--since-commit` flag to your default branch that people merge into (ex: "main"). Set the `--branch` flag to your PR's branch name (ex: "feature-1"). Depending on the CI/CD platform you use, this value can be pulled in dynamically (ex: [CIRCLE_BRANCH in Circle CI](https://circleci.com/docs/variables/) and [TRAVIS_PULL_REQUEST_BRANCH in Travis CI](https://docs.travis-ci.com/user/environment-variables/)). If the repo is cloned and the target branch is already checked out during the CI/CD workflow, then `--branch HEAD` should be sufficient. The `--fail` flag will return an 183 error code if valid credentials are found.
-
-```bash
-trufflehog git file://. --since-commit main --branch feature-1 --results=verified,unknown --fail
-```
-
-## 13: Scan a Postman workspace
-
-Use the `--workspace-id`, `--collection-id`, `--environment` flags multiple times to scan multiple targets.
-
-```bash
-trufflehog postman --token= --workspace-id=
-```
-
-## 14: Scan a Jenkins server
-
-```bash
-trufflehog jenkins --url https://jenkins.example.com --username admin --password admin
-```
-
-## 15: Scan an Elasticsearch server
-
-### Scan a Local Cluster
-
-There are two ways to authenticate to a local cluster with TruffleHog: (1) username and password, (2) service token.
-
-#### Connect to a local cluster with username and password
-
-```bash
-trufflehog elasticsearch --nodes 192.168.14.3 192.168.14.4 --username truffle --password hog
-```
-
-#### Connect to a local cluster with a service token
-
-```bash
-trufflehog elasticsearch --nodes 192.168.14.3 192.168.14.4 --service-token ‘AAEWVaWM...Rva2VuaSDZ’
-```
-
-### Scan an Elastic Cloud Cluster
-
-To scan a cluster on Elastic Cloud, you’ll need a Cloud ID and API key.
-
-```bash
-trufflehog elasticsearch \
- --cloud-id 'search-prod:dXMtY2Vx...YjM1ODNlOWFiZGRlNjI0NA==' \
- --api-key 'MlVtVjBZ...ZSYlduYnF1djh3NG5FQQ=='
-```
-
-## 16. Scan a GitHub Repository for Cross Fork Object References and Deleted Commits
-
-The following command will enumerate deleted and hidden commits on a GitHub repository and then scan them for secrets. This is an alpha release feature.
-
-```bash
-trufflehog github-experimental --repo https://github.com//.git --object-discovery
-```
-
-In addition to the normal TruffleHog output, the `--object-discovery` flag creates two files in a new `$HOME/.trufflehog` directory: `valid_hidden.txt` and `invalid.txt`. These are used to track state during commit enumeration, as well as to provide users with a complete list of all hidden and deleted commits (`valid_hidden.txt`). If you'd like to automatically remove these files after scanning, please add the flag `--delete-cached-data`.
-
-**Note**: Enumerating all valid commits on a repository using this method takes between 20 minutes and a few hours, depending on the size of your repository. We added a progress bar to keep you updated on how long the enumeration will take. The actual secret scanning runs extremely fast.
-
-For more information on Cross Fork Object References, please [read our blog post](https://trufflesecurity.com/blog/anyone-can-access-deleted-and-private-repo-data-github).
-
-## 17. Scan Hugging Face
-
-### Scan a Hugging Face Model, Dataset or Space
-
-```bash
-trufflehog huggingface --model --space --dataset
-```
-
-### Scan all Models, Datasets and Spaces belonging to a Hugging Face Organization or User
-
-```bash
-trufflehog huggingface --org --user
-```
-
-(Optionally) When scanning an organization or user, you can skip an entire class of resources with `--skip-models`, `--skip-datasets`, `--skip-spaces` OR a particular resource with `--ignore-models `, `--ignore-datasets `, `--ignore-spaces `.
-
-### Scan Discussion and PR Comments
-
-```bash
-trufflehog huggingface --model --include-discussions --include-prs
-```
-
-## 18. Scan stdin Input
-
-```bash
-aws s3 cp s3://example/gzipped/data.gz - | gunzip -c | trufflehog stdin
-```
-
-# :question: FAQ
-
-- All I see is `🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷` and the program exits, what gives?
- - That means no secrets were detected
-- Why is the scan taking a long time when I scan a GitHub org
- - Unauthenticated GitHub scans have rate limits. To improve your rate limits, include the `--token` flag with a personal access token
-- It says a private key was verified, what does that mean?
- - A verified result means TruffleHog confirmed the credential is valid by testing it against the service's API. For private keys, we've confirmed the key can be used live for SSH or SSL authentication. Check out our Driftwood blog post to learn more [Blog post](https://trufflesecurity.com/blog/driftwood-know-if-private-keys-are-sensitive/)
-- Is there an easy way to ignore specific secrets?
- - If the scanned source [supports line numbers](https://github.com/trufflesecurity/trufflehog/blob/d6375ba92172fd830abb4247cca15e3176448c5d/pkg/engine/engine.go#L358-L365), then you can add a `trufflehog:ignore` comment on the line containing the secret to ignore that secrets.
-
-# :newspaper: What's new in v3?
-
-TruffleHog v3 is a complete rewrite in Go with many new powerful features.
-
-- We've **added over 700 credential detectors that support active verification against their respective APIs**.
-- We've also added native **support for scanning GitHub, GitLab, Docker, filesystems, S3, GCS, Circle CI and Travis CI**.
-- **Instantly verify private keys** against millions of github users and **billions** of TLS certificates using our [Driftwood](https://trufflesecurity.com/blog/driftwood) technology.
-- Scan binaries, documents, and other file formats
-- Available as a GitHub Action and a pre-commit hook
-
-## What is credential verification?
-
-For every potential credential that is detected, we've painstakingly implemented programmatic verification against the API that we think it belongs to. Verification eliminates false positives and provides three result statuses:
-
-- **verified**: Credential confirmed as valid and active by API testing
-- **unverified**: Credential detected but not confirmed valid (may be invalid, expired, or verification disabled)
-- **unknown**: Verification attempted but failed due to errors, such as a network or API failure
-
-For example, the [AWS credential detector](pkg/detectors/aws/aws.go) performs a `GetCallerIdentity` API call against the AWS API to verify if an AWS credential is active.
-
-# :memo: Usage
-
-TruffleHog has a sub-command for each source of data that you may want to scan:
-
-- git
-- github
-- gitlab
-- docker
-- s3
-- filesystem (files and directories)
-- syslog
-- circleci
-- travisci
-- gcs (Google Cloud Storage)
-- postman
-- jenkins
-- elasticsearch
-- stdin
-- multi-scan
-
-Each subcommand can have options that you can see with the `--help` flag provided to the sub command:
```
-$ trufflehog git --help
-usage: TruffleHog git []
-
-Find credentials in git repositories.
-
-Flags:
- -h, --help Show context-sensitive help (also try --help-long and --help-man).
- --log-level=0 Logging verbosity on a scale of 0 (info) to 5 (trace). Can be disabled with "-1".
- --profile Enables profiling and sets a pprof and fgprof server on :18066.
- -j, --json Output in JSON format.
- --json-legacy Use the pre-v3.0 JSON format. Only works with git, gitlab, and github sources.
- --github-actions Output in GitHub Actions format.
- --concurrency=20 Number of concurrent workers.
- --no-verification Don't verify the results.
- --results=RESULTS Specifies which type(s) of results to output: verified (confirmed valid by API), unknown (verification failed due to error), unverified (detected but not verified), filtered_unverified (unverified but would have been filtered out). Defaults to all types.
- --allow-verification-overlap
- Allow verification of similar credentials across detectors
- --filter-unverified Only output first unverified result per chunk per detector if there are more than one results.
- --filter-entropy=FILTER-ENTROPY
- Filter unverified results with Shannon entropy. Start with 3.0.
- --config=CONFIG Path to configuration file.
- --print-avg-detector-time
- Print the average time spent on each detector.
- --no-update Don't check for updates.
- --fail Exit with code 183 if results are found.
- --verifier=VERIFIER ... Set custom verification endpoints.
- --custom-verifiers-only Only use custom verification endpoints.
- --archive-max-size=ARCHIVE-MAX-SIZE
- Maximum size of archive to scan. (Byte units eg. 512B, 2KB, 4MB)
- --archive-max-depth=ARCHIVE-MAX-DEPTH
- Maximum depth of archive to scan.
- --archive-timeout=ARCHIVE-TIMEOUT
- Maximum time to spend extracting an archive.
- --include-detectors="all" Comma separated list of detector types to include. Protobuf name or IDs may be used, as well as ranges.
- --exclude-detectors=EXCLUDE-DETECTORS
- Comma separated list of detector types to exclude. Protobuf name or IDs may be used, as well as ranges. IDs defined here take precedence over the include list.
- --version Show application version.
- -i, --include-paths=INCLUDE-PATHS
- Path to file with newline separated regexes for files to include in scan.
- -x, --exclude-paths=EXCLUDE-PATHS
- Path to file with newline separated regexes for files to exclude in scan.
- --exclude-globs=EXCLUDE-GLOBS
- Comma separated list of globs to exclude in scan. This option filters at the `git log` level, resulting in faster scans.
- --since-commit=SINCE-COMMIT
- Commit to start scan from.
- --branch=BRANCH Branch to scan.
- --max-depth=MAX-DEPTH Maximum depth of commits to scan.
- --bare Scan bare repository (e.g. useful while using in pre-receive hooks)
-
-Args:
- Git repository URL. https://, file://, or ssh:// schema expected.
+truffleHog --regex --entropy=False https://github.com/dxa4481/truffleHog.git
```
-For example, to scan a `git` repository, start with
+or
```
-trufflehog git https://github.com/trufflesecurity/trufflehog.git
+truffleHog file:///user/dxa4481/codeprojects/truffleHog/
```
-## Configuration
-
-TruffleHog supports defining [custom regex detectors](#custom-regex-detector-alpha)
-and multiple sources in a configuration file provided via the `--config` flag.
-The regex detectors can be used with any subcommand, while the sources defined
-in configuration are only for the `multi-scan` subcommand.
-
-The configuration format for sources can be found on Truffle Security's
-[source configuration documentation page](https://docs.trufflesecurity.com/scan-data-for-secrets).
+With the `--include_paths` and `--exclude_paths` options, it is also possible to limit scanning to a subset of objects in the Git history by defining regular expressions (one per line) in a file to match the targeted object paths. To illustrate, see the example include and exclude files below:
-Example GitHub source configuration and [options reference](https://docs.trufflesecurity.com/github#Fvm1I):
-
-```yaml
-sources:
-- connection:
- '@type': type.googleapis.com/sources.GitHub
- repositories:
- - https://github.com/trufflesecurity/test_keys.git
- unauthenticated: {}
- name: example config scan
- type: SOURCE_TYPE_GITHUB
- verify: true
+_include-patterns.txt:_
+```ini
+src/
+# lines beginning with "#" are treated as comments and are ignored
+gradle/
+# regexes must match the entire path, but can use python's regex syntax for
+# case-insensitive matching and other advanced options
+(?i).*\.(properties|conf|ini|txt|y(a)?ml)$
+(.*/)?id_[rd]sa$
```
-You may define multiple connections under the `sources` key (see above), and
-TruffleHog will scan all of the sources concurrently.
-
-## S3
-
-The S3 source supports assuming IAM roles for scanning in addition to IAM users. This makes it easier for users to scan multiple AWS accounts without needing to rely on hardcoded credentials for each account.
-
-The IAM identity that TruffleHog uses initially will need to have `AssumeRole` privileges as a principal in the [trust policy](https://aws.amazon.com/blogs/security/how-to-use-trust-policies-with-iam-roles/) of each IAM role to assume.
-
-To scan a specific bucket using locally set credentials or instance metadata if on an EC2 instance:
-
-```bash
-trufflehog s3 --bucket=
+_exclude-patterns.txt:_
+```ini
+(.*/)?\.classpath$
+.*\.jmx$
+(.*/)?test/(.*/)?resources/
```
-To scan a specific bucket using an assumed role:
-
+These filter files could then be applied by:
```bash
-trufflehog s3 --bucket= --role-arn=
+trufflehog --include_paths include-patterns.txt --exclude_paths exclude-patterns.txt file://path/to/my/repo.git
```
+With these filters, issues found in files in the root-level `src` directory would be reported, unless they had the `.classpath` or `.jmx` extension, or if they were found in the `src/test/dev/resources/` directory, for example. Additional usage information is provided when calling `trufflehog` with the `-h` or `--help` options.
-Multiple roles can be passed as separate arguments. The following command will attempt to scan every bucket each role has permissions to list in the S3 API:
+These features help cut down on noise, and makes the tool easier to shove into a devops pipeline.
-```bash
-trufflehog s3 --role-arn= --role-arn=
-```
-
-Exit Codes:
-
-- 0: No errors and no results were found.
-- 1: An error was encountered. Sources may not have completed scans.
-- 183: No errors were encountered, but results were found. Will only be returned if `--fail` flag is used.
-
-## :octocat: TruffleHog Github Action
-
-### General Usage
+
+## Install
```
-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: --results=verified,unknown
+pip install truffleHog
```
-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 of how to do it:
+## Customizing
+Custom regexes can be added with the following flag `--rules /path/to/rules`. This should be a json file of the following format:
```
-...
- - 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: --results=verified,unknown
-...
+{
+ "RSA private key": "-----BEGIN EC PRIVATE KEY-----"
+}
```
+Things like subdomain enumeration, s3 bucket detection, and other useful regexes highly custom to the situation can be added.
-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.
+Feel free to also contribute high signal regexes upstream that you think will benefit the community. Things like Azure keys, Twilio keys, Google Compute keys, are welcome, provided a high signal regex can be constructed.
-### Canary detection
+trufflehog's base rule set sources from https://github.com/dxa4481/truffleHogRegexes/blob/master/truffleHogRegexes/regexes.json
-TruffleHog statically detects [https://canarytokens.org/](https://canarytokens.org/).
+## How it works
+This module will go through the entire commit history of each branch, and check each diff from each commit, and check for secrets. This is both by regex and by entropy. For entropy checks, truffleHog will evaluate the shannon entropy for both the base64 char set and hexidecimal char set for every blob of text greater than 20 characters comprised of those character sets in each diff. If at any point a high entropy string >20 characters is detected, it will print to the screen.
-
-
-### Advanced Usage
-
-```yaml
-- name: TruffleHog
- uses: trufflesecurity/trufflehog@main
- with:
- # Repository path
- path:
- # Start scanning from here (usually main branch).
- base:
- # Scan commits until here (usually dev branch).
- head: # optional
- # Extra args to be passed to the trufflehog cli.
- extra_args: --log-level=2 --results=verified,unknown
-```
-
-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.
-
-#### Advanced Usage: Scan entire branch
+## Help
```
-- name: scan-push
- uses: trufflesecurity/trufflehog@main
- with:
- base: ""
- head: ${{ github.ref_name }}
- extra_args: --results=verified,unknown
-```
-
-## TruffleHog GitLab CI
-
-### Example Usage
-
-```yaml
-stages:
- - security
-
-security-secrets:
- stage: security
- allow_failure: false
- image: alpine:latest
- variables:
- SCAN_PATH: "." # Set the relative path in the repo to scan
- before_script:
- - apk add --no-cache git curl jq
- - curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin
- script:
- - trufflehog filesystem "$SCAN_PATH" --results=verified,unknown --fail --json | jq
- rules:
- - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
-```
-
-In the example pipeline above, we're scanning for live secrets in all repository directories and files. This job runs only when the pipeline source is a merge request event, meaning it's triggered when a new merge request is created.
-
-## Pre-commit Hook
-
-TruffleHog can be used in a pre-commit hook to prevent credentials from leaking before they ever leave your computer.
-
-See the [pre-commit hook documentation](PreCommit.md) for more information.
-
-## Custom Regex Detector (alpha)
-
-TruffleHog supports detection and verification of custom regular expressions.
-For detection, at least one **regular expression** and **keyword** is required.
-A **keyword** is a fixed literal string identifier that appears in or around
-the regex to be detected. To allow maximum flexibility for verification, a
-webhook is used containing the regular expression matches.
+usage: trufflehog [-h] [--json] [--regex] [--rules RULES]
+ [--entropy DO_ENTROPY] [--since_commit SINCE_COMMIT]
+ [--max_depth MAX_DEPTH]
+ git_url
-TruffleHog will send a JSON POST request containing the regex matches to a
-configured webhook endpoint. If the endpoint responds with a `200 OK` response
-status code, the secret is considered verified. If verification fails due to network/API errors, the result is marked as unknown.
+Find secrets hidden in the depths of git.
-Custom Detectors support a few different filtering mechanisms: entropy, regex targeting the entire match, regex targeting the captured secret,
-and excluded word lists checked against the secret (captured group if present, entire match if capture group is not present). Note that if
-your custom detector has multiple `regex` set (in this example `hogID`, and `hogToken`), then the filters get applied to each regex. [Here](examples/generic_with_filters.yml) is an example of a custom detector using these filters.
+positional arguments:
+ git_url URL for secret searching
-**NB:** This feature is alpha and subject to change.
-
-### Regex Detector Example
-[Here](/pkg/custom_detectors/CUSTOM_DETECTORS.md) is how to setup a custom regex detector with verification server.
-
-## Generic JWT Detection
-
-TruffleHog supports detection and verification of a subset of generic JWTs it finds.
-Specifically, if a JWT uses public-key cryptography rather than HMAC and the public key can be obtained, TruffleHog can determine whether the JWT is live or not.
-
-## :mag: Analyze
-
-TruffleHog supports running a deeper analysis of a credential to view its permissions and the resources it has access to.
-
-```bash
-trufflehog analyze
+optional arguments:
+ -h, --help show this help message and exit
+ --json Output in JSON
+ --regex Enable high signal regex checks
+ --rules RULES Ignore default regexes and source from json list file
+ --entropy DO_ENTROPY Enable entropy checks
+ --since_commit SINCE_COMMIT
+ Only scan from a given commit hash
+ --max_depth MAX_DEPTH
+ The max commit depth to go back when searching for
+ secrets
+ -i INCLUDE_PATHS_FILE, --include_paths INCLUDE_PATHS_FILE
+ File with regular expressions (one per line), at least
+ one of which must match a Git object path in order for
+ it to be scanned; lines starting with "#" are treated
+ as comments and are ignored. If empty or not provided
+ (default), all Git object paths are included unless
+ otherwise excluded via the --exclude_paths option.
+ -x EXCLUDE_PATHS_FILE, --exclude_paths EXCLUDE_PATHS_FILE
+ File with regular expressions (one per line), none of
+ which may match a Git object path in order for it to
+ be scanned; lines starting with "#" are treated as
+ comments and are ignored. If empty or not provided
+ (default), no Git object paths are excluded unless
+ effectively excluded via the --include_paths option.
```
-# :heart: Contributors
-
-This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
-
-
-
-
-
-# :computer: Contributing
-
-Contributions are very welcome! Please see our [contribution guidelines first](CONTRIBUTING.md).
-
-We no longer accept contributions to TruffleHog v2, but that code is available in the `v2` branch.
-
-## Adding new secret detectors
-
-We have published some [documentation and tooling to get started on adding new secret detectors](hack/docs/Adding_Detectors_external.md). Let's improve detection together!
-
-# Use as a library
-
-Currently, trufflehog is in heavy development and no guarantees can be made on
-the stability of the public APIs at this time.
-
-# License Change
+## Wishlist
-Since v3.0, TruffleHog is released under a AGPL 3 license, included in [`LICENSE`](LICENSE). TruffleHog v3.0 uses none of the previous codebase, but care was taken to preserve backwards compatibility on the command line interface. The work previous to this release is still available licensed under GPL 2.0 in the history of this repository and the previous package releases and tags. A completed CLA is required for us to accept contributions going forward.
+- ~~A way to detect and not scan binary diffs~~
+- ~~Don't rescan diffs if already looked at in another branch~~
+- ~~A since commit X feature~~
+- ~~Print the file affected~~
diff --git a/SECURITY.md b/SECURITY.md
deleted file mode 100644
index f6ae871fcdd8..000000000000
--- a/SECURITY.md
+++ /dev/null
@@ -1 +0,0 @@
-Please report any security issues to security@trufflesec.com and include `trufflehog` in the subject
\ No newline at end of file
diff --git a/action.yml b/action.yml
deleted file mode 100644
index 2acb0ad23f81..000000000000
--- a/action.yml
+++ /dev/null
@@ -1,106 +0,0 @@
-name: 'TruffleHog OSS'
-description: 'Find and verify leaked credentials in your source code.'
-author: Truffle Security Co.
-
-inputs:
- path:
- description: Repository path
- required: false
- default: "./"
- base:
- description: Start scanning from here (usually main branch).
- required: false
- default: ""
- head:
- description: Scan commits until here (usually dev branch).
- required: false
- extra_args:
- default: ""
- description: Extra args to be passed to the trufflehog cli.
- required: false
- version:
- default: "latest"
- description: Scan with this trufflehog cli version.
- required: false
-branding:
- icon: "shield"
- color: "green"
-
-runs:
- using: "composite"
- steps:
- - shell: bash
- working-directory: ${{ inputs.path }}
- env:
- BASE: ${{ inputs.base }}
- HEAD: ${{ inputs.head }}
- ARGS: ${{ inputs.extra_args }}
- COMMIT_IDS: ${{ toJson(github.event.commits.*.id) }}
- VERSION: ${{ inputs.version }}
- run: |
- ##########################################
- ## ADVANCED USAGE ##
- ## Scan by BASE & HEAD user inputs ##
- ## If BASE == HEAD, exit with error ##
- ##########################################
- # Check if jq is installed, if not, install it
- if ! command -v jq &> /dev/null
- then
- echo "jq could not be found, installing..."
- apt-get -y update && apt-get install -y jq
- fi
-
- git status >/dev/null # make sure we are in a git repository
- 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 COMMIT_IDS | 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=""
- 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 .:/tmp -w /tmp \
- ghcr.io/trufflesecurity/trufflehog:${VERSION} \
- git file:///tmp/ \
- --since-commit \
- ${BASE:-''} \
- --branch \
- ${HEAD:-''} \
- --fail \
- --no-update \
- --github-actions \
- ${ARGS:-''}
diff --git a/assets/scanning_logos.svg b/assets/scanning_logos.svg
deleted file mode 100644
index 3964e8607acd..000000000000
--- a/assets/scanning_logos.svg
+++ /dev/null
@@ -1,135 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/concurrency.md b/docs/concurrency.md
deleted file mode 100644
index 15edf73fa069..000000000000
--- a/docs/concurrency.md
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-## Concurrency
-
-```mermaid
-sequenceDiagram
- %% Setup the workers
- participant Main
- Note over Main: e.startWorkers() kicks off some number of threads per worker type
- create participant ScannerWorkers
- Main->>ScannerWorkers: e.startScannerWorkers()
- Note over ScannerWorkers: ScannerWorkers are primarily responsible for enumerating and chunking a source
- create participant VerificationOverlapWorkers
- Main->>VerificationOverlapWorkers: e.startVerificationOverlapWorkers()
- Note over VerificationOverlapWorkers: VerificationOverlapWorkers handles chunks matched to multiple detectors
- create participant DetectorWorkers
- Main->>DetectorWorkers: e.startDetectorWorkers()
- Note over DetectorWorkers: DetectorWorkers are primarily responsible for running detectors on chunks
- create participant NotifierWorkers
- Main->>NotifierWorkers: e.startNotifierWorkers()
- Note over NotifierWorkers: Primarily responsible for reporting results (typically to the cmd line)
-
- %% Set up the parallelism
- par
- Note over Main,ScannerWorkers: Depending on the type of scan requested, calls one of engine.(ScanGit|ScanGitHub|ScanFileSystem|etc)
- Main->>ScannerWorkers: e.ChunksChan() <- chunk
- and
- Note over ScannerWorkers: Decode chunks and find matching detectors
- ScannerWorkers->>DetectorWorkers: e.detectableChunksChan <- detectableChunk
- Note over ScannerWorkers: When multiple detectors match on the same chunk we have to decided _which_ detector will verify found secrets
- ScannerWorkers->>VerificationOverlapWorkers: e.verificationOverlapChunksChan <- verificationOverlapChunk
- and
- Note over VerificationOverlapWorkers: Decide which detectors to run on that chunk
- VerificationOverlapWorkers->>DetectorWorkers: e.detectableChunksChan <- detectableChunk
- and
- Note over DetectorWorkers: Run detection (finding secrets), optionally verify them do filtering and enrichment
- DetectorWorkers->>NotifierWorkers: e.ResultsChan()|e.results <-detectors.ResultWithMetadata
- and
- Note over NotifierWorkers: Write results to output
- end
-
-
-```
\ No newline at end of file
diff --git a/docs/process_flow.md b/docs/process_flow.md
deleted file mode 100644
index c58f2d24f3da..000000000000
--- a/docs/process_flow.md
+++ /dev/null
@@ -1,138 +0,0 @@
-# TruffleHog Process Flows
-
-## Scans
-
-## Data Flow
-
-```mermaid
-flowchart LR
- SourceDecomposition["`**Source Decomposition**
-
-Breaking up the locations that we are looking _for_ secrets into small chunks`"]
-
- DetectorMatching{Chunk to Detector Matching}
-
- SecretDetection["`**Secret Detection**
-
-Finding secrets in these chunks and (optionally) verifying whether they are live`"]
-
- ResultNotification["`**Result Notification**
-
-Enriching results with metadata and (usually) printing to console`"]
-
- SourceDecomposition -- chunks --> DetectorMatching
- DetectorMatching -- matched chunks --> SecretDetection
- SecretDetection -- results --> ResultNotification
-```
-
-#### Source Decomposition
-
-```mermaid
-flowchart TD
- subgraph Source
- direction TB
- SourceDescription("`**(1)** Sources are top level places we find data/files/text to _scan_`")
- GitSource["git Source"]
- GitHubSource["GitHub Source"]
- FilesystemSource["File System Source"]
- PostmanSource["Postman Source"]
- end
-
- subgraph Unit
- direction TB
- UnitDescription("`**(2)** Units are natural subdivisions of Sources, but still quite large`")
- FilesystemUnit[Directory]
- GitUnit[Git Repository]
- end
-
- subgraph Chunk
- direction TB
- ChunkDescription("`**(3)** Chunks are the smallest units that we decompose our chunks into, and are subsequent passed on to detection`")
- FilesystemChunk[file contents]
- GitRepositoryChunk["`git log diff hunks`"]
- PostmanChunk[data chunk]
- end
-
-
- SourceDescription -- decomposed into --> UnitDescription
- UnitDescription -- further decomposed into --> ChunkDescription
-
-
- GitSource -- cloned locally if not already local --> GitUnit
- GitHubSource -- cloned locally --> GitUnit
- PostmanSource -- Most sources\ndon't use units --> PostmanChunk
- FilesystemSource --> FilesystemUnit
-
- GitUnit -- git log -p --> GitRepositoryChunk
- FilesystemUnit --> FilesystemChunk
-
- style SourceDescription fill:#89553e
- style UnitDescription fill:#89553e
- style ChunkDescription fill:#89553e
-```
-
-#### Chunk to Detector Matching
-
-```mermaid
-flowchart LR
-
-
- KeywordMatching["`**Keyword Matching**
-_(Aho-Corasick)_
-
-Match chunks to detectors based on the presence of specific keywords in the chunk`"]
-
- chunks --> KeywordMatching --> detectors
-```
-
-#### Secret Detection
-
-```mermaid
-flowchart LR
-
-subgraph Detector
- direction RL
- subgraph DetectorDescription[" "]
- DetectorDescriptionText["`Detectors are the bits that actually check for the existence of a secret in a chunk, and (optionally) verify it`"]
- ExampleDetectors["`Example Detectors:
- * AWS
- * Azure
- * Twilio`"]
- end
-
- subgraph DetectorResponsibility[" "]
- direction LR
-
- De-Dupe-Detectors["`**De-Dupe-Detectors**
-
-If multiple detectors keyword-match on the same chunk, we have some logic that chooses which detector will verify found secret (so we don't duplicate verification requests to external APIs)`"]
-
- CollectMatches["`**Collect Matches**
-
-Detector specific regexes are run against the matched chunks, resulting in unverified secrets`"]
- VerifyMatches["`**Verify Matches**
-
-Optionally, observed unverified secrets are verified by attempting to use them against live services`"]
-
- De-Dupe-Detectors -- deduped detectors --> CollectMatches
- CollectMatches -- regex matched chunks --> VerifyMatches
- end
-
- style DetectorDescription fill:#89553e
- style DetectorDescriptionText fill:#89553e
-end
-```
-
-#### Result Notification
-
-```mermaid
-flowchart LR
-
- Dispatcher["`**Dispatcher**
-
-Results, verified or otherwise, are sent to a dispatcher to be sent to whichever place we're updating about the
-results -- usually the command line.`"]
-
- results --> Dispatcher --> output
-```
-
diff --git a/entrypoint.sh b/entrypoint.sh
deleted file mode 100755
index d00e865bf548..000000000000
--- a/entrypoint.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env bash
-
-# Parse the last argument into an array of extra_args.
-mapfile -t extra_args < <(bash -c "for arg in ${*: -1}; do echo \$arg; done")
-
-# Directories might be owned by a user other than root
-git config --global --add safe.directory '*'
-
-if [[ $# -eq 0 ]]; then
- /usr/bin/trufflehog --help
-else
- /usr/bin/trufflehog "${@: 1: $#-1}" "${extra_args[@]}"
-fi
diff --git a/examples/README.md b/examples/README.md
deleted file mode 100644
index cb04f5d204be..000000000000
--- a/examples/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# Examples
-This folder contains various examples like custom detectors, scripts, etc. Feel free to contribute!
-
-### Generic Detector
-An often requested feature for TruffleHog is a generic detector. By default, we do not support generic detection as it would result in lots of false positives. However, if you want to attempt detect generic secrets you can use a custom detector.
-
-#### Try it out:
-```
-wget https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/examples/generic.yml
-trufflehog filesystem --config=$PWD/generic.yml $PWD
-
-# to filter so that _only_ generic credentials are logged:
-trufflehog filesystem --config=$PWD/generic.yml --json --no-verification $PWD | awk '/generic-api-key/{print $0}'
-```
diff --git a/examples/generic.yml b/examples/generic.yml
deleted file mode 100644
index 04a226d21f61..000000000000
--- a/examples/generic.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-detectors:
-- name: generic-api-key
- keywords:
- - key
- - api
- - token
- - secret
- - client
- - passwd
- - password
- - auth
- - access
- regex:
- # borrowing the gitleaks generic-api-key regex
- generic-api-key: "(?i)(?:key|api|token|secret|client|passwd|password|auth|access)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:{1,3}=|\\|\\|:|<=|=>|:|\\?=)(?:'|\"|\\s|=|\\x60){0,5}([0-9a-z\\-_.=]{10,150})(?:['|\"|\\n|\\r|\\s|\\x60|;]|$)"
diff --git a/examples/generic_with_filters.yml b/examples/generic_with_filters.yml
deleted file mode 100644
index 2af7e097d0b0..000000000000
--- a/examples/generic_with_filters.yml
+++ /dev/null
@@ -1,1502 +0,0 @@
-detectors:
-- name: generic-password
- keywords:
- - pass
- - access
- - auth
- - credential
- - cred
- - secret
- - token
- regex:
- secret: |-
- (?i)[\w.-]{0,50}?(?:access|auth|(?-i:[Aa]pi|API)|credential|creds|key|passw(?:or)?d|secret|token)(?:[ \t\w.-]{0,20})[\s'"]{0,3}(?:=|>|:{1,3}=|\|\||:|=>|\?=|,)[\x60'"\s=]{0,5}([\w.=-]{10,150}|[a-z0-9][a-z0-9+/]{11,}={0,3})(?:[\x60'"\s;]|\\[nr]|$)
- validations:
- secret: # name of the regex to apply these validations to
- contains_digit: true
- contains_special_char: true
- entropy: 3.5
- # exclude_regexes_capture:
- # - |-
- # (?i)(?:ignore)
- exclude_regexes_match:
- - |-
- (?i)(?:access(?:ibility|or)|access[_.-]?id|random[_.-]?access|api[_.-]?(?:id|name|version)|rapid|capital|[a-z0-9-]*?api[a-z0-9-]*?:jar:|author|X-MS-Exchange-Organization-Auth|Authentication-Results|(?:credentials?[_.-]?id|withCredentials)|(?:bucket|foreign|hot|idx|natural|primary|pub(?:lic)?|schema|sequence)[_.-]?key|key[_.-]?(?:alias|board|code|frame|id|length|mesh|name|pair|ring|selector|signature|size|stone|storetype|word|up|down|left|right)|key[_.-]?vault[_.-]?(?:id|name)|keyVaultToStoreSecrets|key(?:store|tab)[_.-]?(?:file|path)|issuerkeyhash|(?-i:[DdMm]onkey|[DM]ONKEY)|keying|(?:secret)[_.-]?(?:length|name|size)|UserSecretsId|(?:io\.jsonwebtoken[ \t]?:[ \t]?[\w-]+)|(?:api|credentials|token)[_.-]?(?:endpoint|ur[il])|public[_.-]?token|(?:key|token)[_.-]?file|(?-i:(?:[A-Z_]+=\n[A-Z_]+=|[a-z_]+=\n[a-z_]+=)(?:\n|\z))|(?-i:(?:[A-Z.]+=\n[A-Z.]+=|[a-z.]+=\n[a-z.]+=)(?:\n|\z)))
- exclude_words:
- - "exclude"
- - "000000"
- - "aaaaaa"
- - "about"
- - "abstract"
- - "academy"
- - "acces"
- - "account"
- - "act-"
- - "act."
- - "act_"
- - "action"
- - "active"
- - "actively"
- - "activity"
- - "adapter"
- - "add-"
- - "add."
- - "add_"
- - "add-on"
- - "addon"
- - "addres"
- - "admin"
- - "adobe"
- - "advanced"
- - "adventure"
- - "agent"
- - "agile"
- - "air-"
- - "air."
- - "air_"
- - "ajax"
- - "akka"
- - "alert"
- - "alfred"
- - "algorithm"
- - "all-"
- - "all."
- - "all_"
- - "alloy"
- - "alpha"
- - "amazon"
- - "amqp"
- - "analysi"
- - "analytic"
- - "analyzer"
- - "android"
- - "angular"
- - "angularj"
- - "animate"
- - "animation"
- - "another"
- - "ansible"
- - "answer"
- - "ant-"
- - "ant."
- - "ant_"
- - "any-"
- - "any."
- - "any_"
- - "apache"
- - "app-"
- - "app-"
- - "app."
- - "app."
- - "app_"
- - "app_"
- - "apple"
- - "arch"
- - "archive"
- - "archived"
- - "arduino"
- - "array"
- - "art-"
- - "art."
- - "art_"
- - "article"
- - "asp-"
- - "asp."
- - "asp_"
- - "asset"
- - "async"
- - "atom"
- - "attention"
- - "audio"
- - "audit"
- - "aura"
- - "auth"
- - "author"
- - "author"
- - "authorize"
- - "auto"
- - "automated"
- - "automatic"
- - "awesome"
- - "aws_"
- - "azure"
- - "back"
- - "backbone"
- - "backend"
- - "backup"
- - "bar-"
- - "bar."
- - "bar_"
- - "base"
- - "based"
- - "bash"
- - "basic"
- - "batch"
- - "been"
- - "beer"
- - "behavior"
- - "being"
- - "benchmark"
- - "best"
- - "beta"
- - "better"
- - "big-"
- - "big."
- - "big_"
- - "binary"
- - "binding"
- - "bit-"
- - "bit."
- - "bit_"
- - "bitcoin"
- - "block"
- - "blog"
- - "board"
- - "book"
- - "bookmark"
- - "boost"
- - "boot"
- - "bootstrap"
- - "bosh"
- - "bot-"
- - "bot."
- - "bot_"
- - "bower"
- - "box-"
- - "box."
- - "box_"
- - "boxen"
- - "bracket"
- - "branch"
- - "bridge"
- - "browser"
- - "brunch"
- - "buffer"
- - "bug-"
- - "bug."
- - "bug_"
- - "build"
- - "builder"
- - "building"
- - "buildout"
- - "buildpack"
- - "built"
- - "bundle"
- - "busines"
- - "but-"
- - "but."
- - "but_"
- - "button"
- - "cache"
- - "caching"
- - "cakephp"
- - "calendar"
- - "call"
- - "camera"
- - "campfire"
- - "can-"
- - "can."
- - "can_"
- - "canva"
- - "captcha"
- - "capture"
- - "card"
- - "carousel"
- - "case"
- - "cassandra"
- - "cat-"
- - "cat."
- - "cat_"
- - "category"
- - "center"
- - "cento"
- - "challenge"
- - "change"
- - "changelog"
- - "channel"
- - "chart"
- - "chat"
- - "cheat"
- - "check"
- - "checker"
- - "chef"
- - "ches"
- - "chinese"
- - "chosen"
- - "chrome"
- - "ckeditor"
- - "clas"
- - "classe"
- - "classic"
- - "clean"
- - "cli-"
- - "cli."
- - "cli_"
- - "client"
- - "client"
- - "clojure"
- - "clone"
- - "closure"
- - "cloud"
- - "club"
- - "cluster"
- - "cms-"
- - "cms_"
- - "coco"
- - "code"
- - "coding"
- - "coffee"
- - "color"
- - "combination"
- - "combo"
- - "command"
- - "commander"
- - "comment"
- - "commit"
- - "common"
- - "community"
- - "compas"
- - "compiler"
- - "complete"
- - "component"
- - "composer"
- - "computer"
- - "computing"
- - "con-"
- - "con."
- - "con_"
- - "concept"
- - "conf"
- - "config"
- - "config"
- - "connect"
- - "connector"
- - "console"
- - "contact"
- - "container"
- - "contao"
- - "content"
- - "contest"
- - "context"
- - "control"
- - "convert"
- - "converter"
- - "conway'"
- - "cookbook"
- - "cookie"
- - "cool"
- - "copy"
- - "cordova"
- - "core"
- - "couchbase"
- - "couchdb"
- - "countdown"
- - "counter"
- - "course"
- - "craft"
- - "crawler"
- - "create"
- - "creating"
- - "creator"
- - "credential"
- - "crm-"
- - "crm."
- - "crm_"
- - "cros"
- - "crud"
- - "csv-"
- - "csv."
- - "csv_"
- - "cube"
- - "cucumber"
- - "cuda"
- - "current"
- - "currently"
- - "custom"
- - "daemon"
- - "dark"
- - "dart"
- - "dash"
- - "dashboard"
- - "data"
- - "database"
- - "date"
- - "day-"
- - "day."
- - "day_"
- - "dead"
- - "debian"
- - "debug"
- - "debug"
- - "debugger"
- - "deck"
- - "define"
- - "del-"
- - "del."
- - "del_"
- - "delete"
- - "demo"
- - "deploy"
- - "design"
- - "designer"
- - "desktop"
- - "detection"
- - "detector"
- - "dev-"
- - "dev."
- - "dev_"
- - "develop"
- - "developer"
- - "device"
- - "devise"
- - "diff"
- - "digital"
- - "directive"
- - "directory"
- - "discovery"
- - "display"
- - "django"
- - "dns-"
- - "dns_"
- - "doc-"
- - "doc-"
- - "doc."
- - "doc."
- - "doc_"
- - "doc_"
- - "docker"
- - "docpad"
- - "doctrine"
- - "document"
- - "doe-"
- - "doe."
- - "doe_"
- - "dojo"
- - "dom-"
- - "dom."
- - "dom_"
- - "domain"
- - "done"
- - "don't"
- - "dot-"
- - "dot."
- - "dot_"
- - "dotfile"
- - "download"
- - "draft"
- - "drag"
- - "drill"
- - "drive"
- - "driven"
- - "driver"
- - "drop"
- - "dropbox"
- - "drupal"
- - "dsl-"
- - "dsl."
- - "dsl_"
- - "dynamic"
- - "easy"
- - "_ec2_"
- - "ecdsa"
- - "eclipse"
- - "edit"
- - "editing"
- - "edition"
- - "editor"
- - "element"
- - "emac"
- - "email"
- - "embed"
- - "embedded"
- - "ember"
- - "emitter"
- - "emulator"
- - "encoding"
- - "endpoint"
- - "engine"
- - "english"
- - "enhanced"
- - "entity"
- - "entry"
- - "env_"
- - "episode"
- - "erlang"
- - "error"
- - "espresso"
- - "event"
- - "evented"
- - "example"
- - "example"
- - "exchange"
- - "exercise"
- - "experiment"
- - "expire"
- - "exploit"
- - "explorer"
- - "export"
- - "exporter"
- - "expres"
- - "ext-"
- - "ext."
- - "ext_"
- - "extended"
- - "extension"
- - "external"
- - "extra"
- - "extractor"
- - "fabric"
- - "facebook"
- - "factory"
- - "fake"
- - "fast"
- - "feature"
- - "feed"
- - "fewfwef"
- - "ffmpeg"
- - "field"
- - "file"
- - "filter"
- - "find"
- - "finder"
- - "firefox"
- - "firmware"
- - "first"
- - "fish"
- - "fix-"
- - "fix_"
- - "flash"
- - "flask"
- - "flat"
- - "flex"
- - "flexible"
- - "flickr"
- - "flow"
- - "fluent"
- - "fluentd"
- - "fluid"
- - "folder"
- - "font"
- - "force"
- - "foreman"
- - "fork"
- - "form"
- - "format"
- - "formatter"
- - "forum"
- - "foundry"
- - "framework"
- - "free"
- - "friend"
- - "friendly"
- - "front-end"
- - "frontend"
- - "ftp-"
- - "ftp."
- - "ftp_"
- - "fuel"
- - "full"
- - "fun-"
- - "fun."
- - "fun_"
- - "func"
- - "future"
- - "gaia"
- - "gallery"
- - "game"
- - "gateway"
- - "gem-"
- - "gem."
- - "gem_"
- - "gen-"
- - "gen."
- - "gen_"
- - "general"
- - "generator"
- - "generic"
- - "genetic"
- - "get-"
- - "get."
- - "get_"
- - "getenv"
- - "getting"
- - "ghost"
- - "gist"
- - "git-"
- - "git."
- - "git_"
- - "github"
- - "gitignore"
- - "gitlab"
- - "glas"
- - "gmail"
- - "gnome"
- - "gnu-"
- - "gnu."
- - "gnu_"
- - "goal"
- - "golang"
- - "gollum"
- - "good"
- - "google"
- - "gpu-"
- - "gpu."
- - "gpu_"
- - "gradle"
- - "grail"
- - "graph"
- - "graphic"
- - "great"
- - "grid"
- - "groovy"
- - "group"
- - "grunt"
- - "guard"
- - "gui-"
- - "gui."
- - "gui_"
- - "guide"
- - "guideline"
- - "gulp"
- - "gwt-"
- - "gwt."
- - "gwt_"
- - "hack"
- - "hackathon"
- - "hacker"
- - "hacking"
- - "hadoop"
- - "haml"
- - "handler"
- - "hardware"
- - "has-"
- - "has_"
- - "hash"
- - "haskell"
- - "have"
- - "haxe"
- - "hello"
- - "help"
- - "helper"
- - "here"
- - "hero"
- - "heroku"
- - "high"
- - "hipchat"
- - "history"
- - "home"
- - "homebrew"
- - "homepage"
- - "hook"
- - "host"
- - "hosting"
- - "hot-"
- - "hot."
- - "hot_"
- - "house"
- - "how-"
- - "how."
- - "how_"
- - "html"
- - "http"
- - "hub-"
- - "hub."
- - "hub_"
- - "hubot"
- - "human"
- - "icon"
- - "ide-"
- - "ide."
- - "ide_"
- - "idea"
- - "identity"
- - "idiomatic"
- - "image"
- - "impact"
- - "import"
- - "important"
- - "importer"
- - "impres"
- - "index"
- - "infinite"
- - "info"
- - "injection"
- - "inline"
- - "input"
- - "inside"
- - "inspector"
- - "instagram"
- - "install"
- - "installer"
- - "instant"
- - "intellij"
- - "interface"
- - "internet"
- - "interview"
- - "into"
- - "intro"
- - "ionic"
- - "iphone"
- - "ipython"
- - "irc-"
- - "irc_"
- - "iso-"
- - "iso."
- - "iso_"
- - "issue"
- - "jade"
- - "jasmine"
- - "java"
- - "jbos"
- - "jekyll"
- - "jenkin"
- - "jetbrains"
- - "job-"
- - "job."
- - "job_"
- - "joomla"
- - "jpa-"
- - "jpa."
- - "jpa_"
- - "jquery"
- - "json"
- - "just"
- - "kafka"
- - "karma"
- - "kata"
- - "kernel"
- - "keyboard"
- - "kindle"
- - "kit-"
- - "kit."
- - "kit_"
- - "kitchen"
- - "knife"
- - "koan"
- - "kohana"
- - "lab-"
- - "lab-"
- - "lab."
- - "lab."
- - "lab_"
- - "lab_"
- - "lambda"
- - "lamp"
- - "language"
- - "laravel"
- - "last"
- - "latest"
- - "latex"
- - "launcher"
- - "layer"
- - "layout"
- - "lazy"
- - "ldap"
- - "leaflet"
- - "league"
- - "learn"
- - "learning"
- - "led-"
- - "led."
- - "led_"
- - "leetcode"
- - "les-"
- - "les."
- - "les_"
- - "level"
- - "leveldb"
- - "lib-"
- - "lib."
- - "lib_"
- - "librarie"
- - "library"
- - "license"
- - "life"
- - "liferay"
- - "light"
- - "lightbox"
- - "like"
- - "line"
- - "link"
- - "linked"
- - "linkedin"
- - "linux"
- - "lisp"
- - "list"
- - "lite"
- - "little"
- - "load"
- - "loader"
- - "local"
- - "location"
- - "lock"
- - "log-"
- - "log."
- - "log_"
- - "logger"
- - "logging"
- - "logic"
- - "login"
- - "logstash"
- - "longer"
- - "look"
- - "love"
- - "lua-"
- - "lua."
- - "lua_"
- - "mac-"
- - "mac."
- - "mac_"
- - "machine"
- - "made"
- - "magento"
- - "magic"
- - "mail"
- - "make"
- - "maker"
- - "making"
- - "man-"
- - "man."
- - "man_"
- - "manage"
- - "manager"
- - "manifest"
- - "manual"
- - "map-"
- - "map-"
- - "map."
- - "map."
- - "map_"
- - "map_"
- - "mapper"
- - "mapping"
- - "markdown"
- - "markup"
- - "master"
- - "math"
- - "matrix"
- - "maven"
- - "md5"
- - "mean"
- - "media"
- - "mediawiki"
- - "meetup"
- - "memcached"
- - "memory"
- - "menu"
- - "merchant"
- - "message"
- - "messaging"
- - "meta"
- - "metadata"
- - "meteor"
- - "method"
- - "metric"
- - "micro"
- - "middleman"
- - "migration"
- - "minecraft"
- - "miner"
- - "mini"
- - "minimal"
- - "mirror"
- - "mit-"
- - "mit."
- - "mit_"
- - "mobile"
- - "mocha"
- - "mock"
- - "mod-"
- - "mod."
- - "mod_"
- - "mode"
- - "model"
- - "modern"
- - "modular"
- - "module"
- - "modx"
- - "money"
- - "mongo"
- - "mongodb"
- - "mongoid"
- - "mongoose"
- - "monitor"
- - "monkey"
- - "more"
- - "motion"
- - "moved"
- - "movie"
- - "mozilla"
- - "mqtt"
- - "mule"
- - "multi"
- - "multiple"
- - "music"
- - "mustache"
- - "mvc-"
- - "mvc."
- - "mvc_"
- - "mysql"
- - "nagio"
- - "name"
- - "native"
- - "need"
- - "neo-"
- - "neo."
- - "neo_"
- - "nest"
- - "nested"
- - "net-"
- - "net."
- - "net_"
- - "nette"
- - "network"
- - "new-"
- - "new-"
- - "new."
- - "new."
- - "new_"
- - "new_"
- - "next"
- - "nginx"
- - "ninja"
- - "nlp-"
- - "nlp."
- - "nlp_"
- - "node"
- - "nodej"
- - "nosql"
- - "not-"
- - "not."
- - "not_"
- - "note"
- - "notebook"
- - "notepad"
- - "notice"
- - "notifier"
- - "now-"
- - "now."
- - "now_"
- - "number"
- - "oauth"
- - "object"
- - "objective"
- - "obsolete"
- - "ocaml"
- - "octopres"
- - "official"
- - "old-"
- - "old."
- - "old_"
- - "onboard"
- - "online"
- - "only"
- - "open"
- - "opencv"
- - "opengl"
- - "openshift"
- - "openwrt"
- - "option"
- - "oracle"
- - "org-"
- - "org."
- - "org_"
- - "origin"
- - "original"
- - "orm-"
- - "orm."
- - "orm_"
- - "osx-"
- - "osx_"
- - "our-"
- - "our."
- - "our_"
- - "out-"
- - "out."
- - "out_"
- - "output"
- - "over"
- - "overview"
- - "own-"
- - "own."
- - "own_"
- - "pack"
- - "package"
- - "packet"
- - "page"
- - "page"
- - "panel"
- - "paper"
- - "paperclip"
- - "para"
- - "parallax"
- - "parallel"
- - "parse"
- - "parser"
- - "parsing"
- - "particle"
- - "party"
- - "password"
- - "patch"
- - "path"
- - "pattern"
- - "payment"
- - "paypal"
- - "pdf-"
- - "pdf."
- - "pdf_"
- - "pebble"
- - "people"
- - "perl"
- - "personal"
- - "phalcon"
- - "phoenix"
- - "phone"
- - "phonegap"
- - "photo"
- - "php-"
- - "php."
- - "php_"
- - "physic"
- - "picker"
- - "pipeline"
- - "platform"
- - "play"
- - "player"
- - "please"
- - "plu-"
- - "plu."
- - "plu_"
- - "plug-in"
- - "plugin"
- - "plupload"
- - "png-"
- - "png."
- - "png_"
- - "poker"
- - "polyfill"
- - "polymer"
- - "pool"
- - "pop-"
- - "pop."
- - "pop_"
- - "popcorn"
- - "popup"
- - "port"
- - "portable"
- - "portal"
- - "portfolio"
- - "post"
- - "power"
- - "powered"
- - "powerful"
- - "prelude"
- - "pretty"
- - "preview"
- - "principle"
- - "print"
- - "pro-"
- - "pro."
- - "pro_"
- - "problem"
- - "proc"
- - "product"
- - "profile"
- - "profiler"
- - "program"
- - "progres"
- - "project"
- - "protocol"
- - "prototype"
- - "provider"
- - "proxy"
- - "public"
- - "pull"
- - "puppet"
- - "pure"
- - "purpose"
- - "push"
- - "pusher"
- - "pyramid"
- - "python"
- - "quality"
- - "query"
- - "queue"
- - "quick"
- - "rabbitmq"
- - "rack"
- - "radio"
- - "rail"
- - "railscast"
- - "random"
- - "range"
- - "raspberry"
- - "rdf-"
- - "rdf."
- - "rdf_"
- - "react"
- - "reactive"
- - "read"
- - "reader"
- - "readme"
- - "ready"
- - "real"
- - "reality"
- - "real-time"
- - "realtime"
- - "recipe"
- - "recorder"
- - "red-"
- - "red."
- - "red_"
- - "reddit"
- - "redi"
- - "redmine"
- - "reference"
- - "refinery"
- - "refresh"
- - "registry"
- - "related"
- - "release"
- - "remote"
- - "rendering"
- - "repo"
- - "report"
- - "request"
- - "require"
- - "required"
- - "requirej"
- - "research"
- - "resource"
- - "response"
- - "resque"
- - "rest"
- - "restful"
- - "resume"
- - "reveal"
- - "reverse"
- - "review"
- - "riak"
- - "rich"
- - "right"
- - "ring"
- - "robot"
- - "role"
- - "room"
- - "router"
- - "routing"
- - "rpc-"
- - "rpc."
- - "rpc_"
- - "rpg-"
- - "rpg."
- - "rpg_"
- - "rspec"
- - "ruby-"
- - "ruby."
- - "ruby_"
- - "rule"
- - "run-"
- - "run."
- - "run_"
- - "runner"
- - "running"
- - "runtime"
- - "rust"
- - "rvm-"
- - "rvm."
- - "rvm_"
- - "salt"
- - "sample"
- - "sample"
- - "sandbox"
- - "sas-"
- - "sas."
- - "sas_"
- - "sbt-"
- - "sbt."
- - "sbt_"
- - "scala"
- - "scalable"
- - "scanner"
- - "schema"
- - "scheme"
- - "school"
- - "science"
- - "scraper"
- - "scratch"
- - "screen"
- - "script"
- - "scroll"
- - "scs-"
- - "scs."
- - "scs_"
- - "sdk-"
- - "sdk."
- - "sdk_"
- - "sdl-"
- - "sdl."
- - "sdl_"
- - "search"
- - "secure"
- - "security"
- - "see-"
- - "see."
- - "see_"
- - "seed"
- - "select"
- - "selector"
- - "selenium"
- - "semantic"
- - "sencha"
- - "send"
- - "sentiment"
- - "serie"
- - "server"
- - "service"
- - "session"
- - "set-"
- - "set."
- - "set_"
- - "setting"
- - "setting"
- - "setup"
- - "sha1"
- - "sha2"
- - "sha256"
- - "share"
- - "shared"
- - "sharing"
- - "sheet"
- - "shell"
- - "shield"
- - "shipping"
- - "shop"
- - "shopify"
- - "shortener"
- - "should"
- - "show"
- - "showcase"
- - "side"
- - "silex"
- - "simple"
- - "simulator"
- - "single"
- - "site"
- - "skeleton"
- - "sketch"
- - "skin"
- - "slack"
- - "slide"
- - "slider"
- - "slim"
- - "small"
- - "smart"
- - "smtp"
- - "snake"
- - "snapshot"
- - "snippet"
- - "soap"
- - "social"
- - "socket"
- - "software"
- - "solarized"
- - "solr"
- - "solution"
- - "solver"
- - "some"
- - "soon"
- - "source"
- - "space"
- - "spark"
- - "spatial"
- - "spec"
- - "sphinx"
- - "spine"
- - "spotify"
- - "spree"
- - "spring"
- - "sprite"
- - "sql-"
- - "sql."
- - "sql_"
- - "sqlite"
- - "ssh-"
- - "ssh."
- - "ssh_"
- - "stack"
- - "staging"
- - "standard"
- - "stanford"
- - "start"
- - "started"
- - "starter"
- - "startup"
- - "stat"
- - "statamic"
- - "state"
- - "static"
- - "statistic"
- - "statsd"
- - "statu"
- - "steam"
- - "step"
- - "still"
- - "stm-"
- - "stm."
- - "stm_"
- - "storage"
- - "store"
- - "storm"
- - "story"
- - "strategy"
- - "stream"
- - "streaming"
- - "string"
- - "stripe"
- - "structure"
- - "studio"
- - "study"
- - "stuff"
- - "style"
- - "sublime"
- - "sugar"
- - "suite"
- - "summary"
- - "super"
- - "support"
- - "supported"
- - "svg-"
- - "svg."
- - "svg_"
- - "svn-"
- - "svn."
- - "svn_"
- - "swagger"
- - "swift"
- - "switch"
- - "switcher"
- - "symfony"
- - "symphony"
- - "sync"
- - "synopsi"
- - "syntax"
- - "system"
- - "system"
- - "tab-"
- - "tab-"
- - "tab."
- - "tab."
- - "tab_"
- - "tab_"
- - "table"
- - "tag-"
- - "tag-"
- - "tag."
- - "tag."
- - "tag_"
- - "tag_"
- - "talk"
- - "target"
- - "task"
- - "tcp-"
- - "tcp."
- - "tcp_"
- - "tdd-"
- - "tdd."
- - "tdd_"
- - "team"
- - "tech"
- - "template"
- - "term"
- - "terminal"
- - "testing"
- - "tetri"
- - "text"
- - "textmate"
- - "theme"
- - "theory"
- - "three"
- - "thrift"
- - "time"
- - "timeline"
- - "timer"
- - "tiny"
- - "tinymce"
- - "tip-"
- - "tip."
- - "tip_"
- - "title"
- - "todo"
- - "todomvc"
- - "token"
- - "tool"
- - "toolbox"
- - "toolkit"
- - "top-"
- - "top."
- - "top_"
- - "tornado"
- - "touch"
- - "tower"
- - "tracker"
- - "tracking"
- - "traffic"
- - "training"
- - "transfer"
- - "translate"
- - "transport"
- - "tree"
- - "trello"
- - "try-"
- - "try."
- - "try_"
- - "tumblr"
- - "tut-"
- - "tut."
- - "tut_"
- - "tutorial"
- - "tweet"
- - "twig"
- - "twitter"
- - "type"
- - "typo"
- - "ubuntu"
- - "uiview"
- - "ultimate"
- - "under"
- - "unit"
- - "unity"
- - "universal"
- - "unix"
- - "update"
- - "updated"
- - "upgrade"
- - "upload"
- - "uploader"
- - "uri-"
- - "uri."
- - "uri_"
- - "url-"
- - "url."
- - "url_"
- - "usage"
- - "usb-"
- - "usb."
- - "usb_"
- - "use-"
- - "use."
- - "use_"
- - "used"
- - "useful"
- - "user"
- - "using"
- - "util"
- - "utilitie"
- - "utility"
- - "vagrant"
- - "validator"
- - "value"
- - "variou"
- - "varnish"
- - "version"
- - "via-"
- - "via."
- - "via_"
- - "video"
- - "view"
- - "viewer"
- - "vim-"
- - "vim."
- - "vim_"
- - "vimrc"
- - "virtual"
- - "vision"
- - "visual"
- - "vpn"
- - "want"
- - "warning"
- - "watch"
- - "watcher"
- - "wave"
- - "way-"
- - "way."
- - "way_"
- - "weather"
- - "web-"
- - "web_"
- - "webapp"
- - "webgl"
- - "webhook"
- - "webkit"
- - "webrtc"
- - "website"
- - "websocket"
- - "welcome"
- - "welcome"
- - "what"
- - "what'"
- - "when"
- - "where"
- - "which"
- - "why-"
- - "why."
- - "why_"
- - "widget"
- - "wifi"
- - "wiki"
- - "win-"
- - "win."
- - "win_"
- - "window"
- - "wip-"
- - "wip."
- - "wip_"
- - "within"
- - "without"
- - "wizard"
- - "word"
- - "wordpres"
- - "work"
- - "worker"
- - "workflow"
- - "working"
- - "workshop"
- - "world"
- - "wrapper"
- - "write"
- - "writer"
- - "writing"
- - "written"
- - "www-"
- - "www."
- - "www_"
- - "xamarin"
- - "xcode"
- - "xml-"
- - "xml."
- - "xml_"
- - "xmpp"
- - "xxxxxx"
- - "yahoo"
- - "yaml"
- - "yandex"
- - "yeoman"
- - "yet-"
- - "yet."
- - "yet_"
- - "yii-"
- - "yii."
- - "yii_"
- - "youtube"
- - "yui-"
- - "yui."
- - "yui_"
- - "zend"
- - "zero"
- - "zip-"
- - "zip."
- - "zip_"
- - "zsh-"
- - "zsh."
- - "zsh_"
\ No newline at end of file
diff --git a/go.mod b/go.mod
deleted file mode 100644
index 290a6db5935b..000000000000
--- a/go.mod
+++ /dev/null
@@ -1,328 +0,0 @@
-module github.com/trufflesecurity/trufflehog/v3
-
-go 1.24.0
-
-toolchain go1.24.5
-
-replace github.com/jpillora/overseer => github.com/trufflesecurity/overseer v1.2.8
-
-// Coinbase archived this library and it has some vulnerable dependencies so we've forked.
-replace github.com/coinbase/waas-client-library-go => github.com/trufflesecurity/waas-client-library-go v1.0.9
-
-// TODO: v1.134.0 is available but deprecates existing Auth methods, should be updated separately
-replace gitlab.com/gitlab-org/api/client-go => gitlab.com/gitlab-org/api/client-go v0.129.0
-
-require (
- cloud.google.com/go/secretmanager v1.15.0
- cloud.google.com/go/storage v1.56.1
- github.com/BobuSumisu/aho-corasick v1.0.3
- github.com/TheZeroSlave/zapsentry v1.23.0
- github.com/adrg/strutil v0.3.1
- github.com/alecthomas/kingpin/v2 v2.4.0
- github.com/avast/apkparser v0.0.0-20250626104540-d53391f4d69d
- github.com/aws/aws-sdk-go-v2 v1.39.0
- github.com/aws/aws-sdk-go-v2/config v1.31.7
- github.com/aws/aws-sdk-go-v2/credentials v1.18.11
- github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.5
- github.com/aws/aws-sdk-go-v2/service/s3 v1.88.0
- github.com/aws/aws-sdk-go-v2/service/sns v1.38.2
- github.com/aws/aws-sdk-go-v2/service/sts v1.38.3
- github.com/aws/smithy-go v1.23.0
- github.com/aymanbagabas/go-osc52 v1.2.1
- github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c
- github.com/bradleyfalzon/ghinstallation/v2 v2.16.0
- github.com/brianvoe/gofakeit/v7 v7.6.0
- github.com/charmbracelet/bubbles v0.18.0
- github.com/charmbracelet/bubbletea v1.3.6
- github.com/charmbracelet/glamour v0.10.0
- github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
- github.com/couchbase/gocb/v2 v2.11.0
- github.com/crewjam/rfc5424 v0.1.0
- github.com/csnewman/dextk v0.3.0
- github.com/docker/docker v28.3.3+incompatible
- github.com/dustin/go-humanize v1.0.1
- github.com/elastic/go-elasticsearch/v8 v8.17.1
- github.com/envoyproxy/protoc-gen-validate v1.2.1
- github.com/fatih/color v1.18.0
- github.com/felixge/fgprof v0.9.5
- github.com/gabriel-vasile/mimetype v1.4.10
- github.com/getsentry/sentry-go v0.32.0
- github.com/go-errors/errors v1.5.1
- github.com/go-git/go-git/v5 v5.13.2
- github.com/go-ldap/ldap/v3 v3.4.11
- github.com/go-logr/logr v1.4.3
- github.com/go-logr/zapr v1.3.0
- github.com/go-redis/redis v6.15.9+incompatible
- github.com/go-sql-driver/mysql v1.8.1
- github.com/gobwas/glob v0.2.3
- github.com/golang-jwt/jwt/v5 v5.2.3
- github.com/google/go-cmp v0.7.0
- github.com/google/go-containerregistry v0.20.6
- github.com/google/go-github/v67 v67.0.0
- github.com/google/uuid v1.6.0
- github.com/googleapis/gax-go/v2 v2.15.0
- github.com/hashicorp/go-retryablehttp v0.7.8
- github.com/hashicorp/golang-lru/v2 v2.0.7
- github.com/jedib0t/go-pretty/v6 v6.6.8
- github.com/jlaffaye/ftp v0.2.0
- github.com/joho/godotenv v1.5.1
- github.com/jpillora/overseer v1.1.6
- github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213
- github.com/klauspost/pgzip v1.2.6
- github.com/kylelemons/godebug v1.1.0
- github.com/lestrrat-go/jwx/v3 v3.0.12
- github.com/lib/pq v1.10.9
- github.com/lrstanley/bubblezone v0.0.0-20250404061050-e13639e27357
- github.com/marusama/semaphore/v2 v2.5.0
- github.com/mattn/go-isatty v0.0.20
- github.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d
- github.com/microsoft/go-mssqldb v1.8.2
- github.com/mitchellh/go-ps v1.0.0
- github.com/muesli/reflow v0.3.0
- github.com/patrickmn/go-cache v2.1.0+incompatible
- github.com/paulbellamy/ratecounter v0.2.0
- github.com/pkg/errors v0.9.1
- github.com/prometheus/client_golang v1.20.5
- github.com/rabbitmq/amqp091-go v1.10.0
- github.com/repeale/fp-go v0.11.1
- github.com/sassoftware/go-rpmutils v0.4.0
- github.com/schollz/progressbar/v3 v3.17.1
- github.com/sendgrid/sendgrid-go v3.16.1+incompatible
- github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
- github.com/shuheiktgw/go-travis v0.3.1
- github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7
- github.com/stretchr/testify v1.11.1
- github.com/testcontainers/testcontainers-go v0.34.0
- github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.34.0
- github.com/testcontainers/testcontainers-go/modules/mongodb v0.34.0
- github.com/testcontainers/testcontainers-go/modules/mssql v0.34.0
- github.com/testcontainers/testcontainers-go/modules/mysql v0.34.0
- github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0
- github.com/trufflesecurity/disk-buffer-reader v0.2.1
- github.com/wasilibs/go-re2 v1.9.0
- github.com/xo/dburl v0.23.8
- gitlab.com/gitlab-org/api/client-go v0.129.0
- go.mongodb.org/mongo-driver v1.17.4
- go.uber.org/automaxprocs v1.6.0
- go.uber.org/mock v0.5.2
- go.uber.org/zap v1.27.0
- golang.org/x/crypto v0.45.0
- golang.org/x/net v0.47.0
- golang.org/x/oauth2 v0.30.0
- golang.org/x/sync v0.18.0
- golang.org/x/text v0.31.0
- golang.org/x/time v0.12.0
- google.golang.org/api v0.247.0
- google.golang.org/protobuf v1.36.9
- gopkg.in/h2non/gock.v1 v1.1.2
- gopkg.in/yaml.v2 v2.4.0
- gopkg.in/yaml.v3 v3.0.1
- pault.ag/go/debian v0.18.0
- pgregory.net/rapid v1.1.0
- sigs.k8s.io/yaml v1.4.0
-)
-
-require (
- cel.dev/expr v0.24.0 // indirect
- cloud.google.com/go v0.121.6 // indirect
- cloud.google.com/go/auth v0.16.5 // indirect
- cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
- cloud.google.com/go/compute/metadata v0.8.0 // indirect
- cloud.google.com/go/iam v1.5.2 // indirect
- cloud.google.com/go/monitoring v1.24.2 // indirect
- dario.cat/mergo v1.0.0 // indirect
- filippo.io/edwards25519 v1.1.0 // indirect
- github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
- github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
- github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
- github.com/DataDog/zstd v1.5.5 // indirect
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
- github.com/Microsoft/go-winio v0.6.2 // indirect
- github.com/ProtonMail/go-crypto v1.1.5 // indirect
- github.com/STARRY-S/zip v0.2.1 // indirect
- github.com/alecthomas/chroma/v2 v2.14.0 // indirect
- github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
- github.com/andybalholm/brotli v1.1.1 // indirect
- github.com/atotto/clipboard v0.1.4 // indirect
- github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect
- github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 // indirect
- github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 // indirect
- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 // indirect
- github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
- github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 // indirect
- github.com/aws/aws-sdk-go-v2/service/sso v1.29.2 // indirect
- github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.3 // indirect
- github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
- github.com/aymerick/douceur v0.2.0 // indirect
- github.com/beorn7/perks v1.0.1 // indirect
- github.com/bodgit/plumbing v1.3.0 // indirect
- github.com/bodgit/sevenzip v1.6.0 // indirect
- github.com/bodgit/windows v1.0.1 // indirect
- github.com/cenkalti/backoff/v4 v4.2.1 // indirect
- github.com/cespare/xxhash/v2 v2.3.0 // indirect
- github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
- github.com/charmbracelet/x/ansi v0.9.3 // indirect
- github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
- github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
- github.com/charmbracelet/x/term v0.2.1 // indirect
- github.com/cloudflare/circl v1.6.1 // indirect
- github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
- github.com/containerd/errdefs v1.0.0 // indirect
- github.com/containerd/errdefs/pkg v0.3.0 // indirect
- github.com/containerd/log v0.1.0 // indirect
- github.com/containerd/platforms v0.2.1 // indirect
- github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
- github.com/couchbase/gocbcore/v10 v10.8.0 // indirect
- github.com/couchbase/gocbcoreps v0.1.3 // indirect
- github.com/couchbase/goprotostellar v1.0.2 // indirect
- github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20240607131231-fb385523de28 // indirect
- github.com/cpuguy83/dockercfg v0.3.2 // indirect
- github.com/cyphar/filepath-securejoin v0.3.6 // indirect
- github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
- github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
- github.com/distribution/reference v0.6.0 // indirect
- github.com/dlclark/regexp2 v1.11.0 // indirect
- github.com/docker/cli v28.2.2+incompatible // indirect
- github.com/docker/distribution v2.8.3+incompatible // indirect
- github.com/docker/docker-credential-helpers v0.9.3 // indirect
- github.com/docker/go-connections v0.5.0 // indirect
- github.com/docker/go-units v0.5.0 // indirect
- github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
- github.com/elastic/elastic-transport-go/v8 v8.6.1 // indirect
- github.com/emirpasic/gods v1.18.1 // indirect
- github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
- github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
- github.com/felixge/httpsnoop v1.0.4 // indirect
- github.com/fsnotify/fsnotify v1.6.0 // indirect
- github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
- github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
- github.com/go-git/go-billy/v5 v5.6.2 // indirect
- github.com/go-jose/go-jose/v4 v4.0.5 // indirect
- github.com/go-logr/stdr v1.2.2 // indirect
- github.com/go-ole/go-ole v1.2.6 // indirect
- github.com/goccy/go-json v0.10.3 // indirect
- github.com/gofrs/flock v0.12.1 // indirect
- github.com/gogo/protobuf v1.3.2 // indirect
- github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
- github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
- github.com/golang-sql/sqlexp v0.1.0 // indirect
- github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
- github.com/golang/snappy v1.0.0 // indirect
- github.com/google/go-github/v72 v72.0.0 // indirect
- github.com/google/go-querystring v1.1.0 // indirect
- github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
- github.com/google/s2a-go v0.1.9 // indirect
- github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
- github.com/gorilla/css v1.0.1 // indirect
- github.com/gorilla/websocket v1.5.3 // indirect
- github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
- github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
- github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
- github.com/hashicorp/errwrap v1.1.0 // indirect
- github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
- github.com/hashicorp/go-multierror v1.1.1 // indirect
- github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
- github.com/jpillora/s3 v1.1.4 // indirect
- github.com/kevinburke/ssh_config v1.2.0 // indirect
- github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d // indirect
- github.com/klauspost/compress v1.18.0 // indirect
- github.com/lestrrat-go/blackmagic v1.0.4 // indirect
- github.com/lestrrat-go/httpcc v1.0.1 // indirect
- github.com/lestrrat-go/httprc/v3 v3.0.1 // indirect
- github.com/lestrrat-go/option v1.0.1 // indirect
- github.com/lestrrat-go/option/v2 v2.0.0 // indirect
- github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
- github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
- github.com/magiconair/properties v1.8.7 // indirect
- github.com/mattn/go-colorable v0.1.13 // indirect
- github.com/mattn/go-localereader v0.0.1 // indirect
- github.com/mattn/go-runewidth v0.0.16 // indirect
- github.com/microcosm-cc/bluemonday v1.0.27 // indirect
- github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
- github.com/mitchellh/go-homedir v1.1.0 // indirect
- github.com/moby/docker-image-spec v1.3.1 // indirect
- github.com/moby/go-archive v0.1.0 // indirect
- github.com/moby/patternmatcher v0.6.0 // indirect
- github.com/moby/sys/sequential v0.6.0 // indirect
- github.com/moby/sys/user v0.4.0 // indirect
- github.com/moby/sys/userns v0.1.0 // indirect
- github.com/moby/term v0.5.0 // indirect
- github.com/montanaflynn/stats v0.7.1 // indirect
- github.com/morikuni/aec v1.0.0 // indirect
- github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
- github.com/muesli/cancelreader v0.2.2 // indirect
- github.com/muesli/termenv v0.16.0 // indirect
- github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
- github.com/nwaples/rardecode/v2 v2.2.1 // indirect
- github.com/onsi/ginkgo v1.16.5 // indirect
- github.com/opencontainers/go-digest v1.0.0 // indirect
- github.com/opencontainers/image-spec v1.1.1 // indirect
- github.com/pierrec/lz4/v4 v4.1.21 // indirect
- github.com/pjbgf/sha1cd v0.3.2 // indirect
- github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
- github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
- github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
- github.com/prometheus/client_model v0.6.1 // indirect
- github.com/prometheus/common v0.55.0 // indirect
- github.com/prometheus/procfs v0.15.1 // indirect
- github.com/rivo/uniseg v0.4.7 // indirect
- github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
- github.com/segmentio/asm v1.2.1 // indirect
- github.com/sendgrid/rest v2.6.9+incompatible // indirect
- github.com/shirou/gopsutil/v3 v3.23.12 // indirect
- github.com/shoenig/go-m1cpu v0.1.6 // indirect
- github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
- github.com/sirupsen/logrus v1.9.3 // indirect
- github.com/skeema/knownhosts v1.3.0 // indirect
- github.com/sorairolake/lzip-go v0.3.5 // indirect
- github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
- github.com/stretchr/objx v0.5.2 // indirect
- github.com/tetratelabs/wazero v1.9.0 // indirect
- github.com/therootcompany/xz v1.0.1 // indirect
- github.com/tklauser/go-sysconf v0.3.12 // indirect
- github.com/tklauser/numcpus v0.6.1 // indirect
- github.com/trufflesecurity/touchfile v0.1.1 // indirect
- github.com/ulikunitz/xz v0.5.12 // indirect
- github.com/vbatts/tar-split v0.12.1 // indirect
- github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect
- github.com/xanzy/ssh-agent v0.3.3 // indirect
- github.com/xdg-go/pbkdf2 v1.0.0 // indirect
- github.com/xdg-go/scram v1.1.2 // indirect
- github.com/xdg-go/stringprep v1.0.4 // indirect
- github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
- github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
- github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
- github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
- github.com/yuin/goldmark v1.7.8 // indirect
- github.com/yuin/goldmark-emoji v1.0.5 // indirect
- github.com/yusufpapurcu/wmi v1.2.3 // indirect
- github.com/zeebo/errs v1.4.0 // indirect
- go.opentelemetry.io/auto/sdk v1.1.0 // indirect
- go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
- go.opentelemetry.io/otel v1.37.0 // indirect
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
- go.opentelemetry.io/otel/metric v1.37.0 // indirect
- go.opentelemetry.io/otel/sdk v1.37.0 // indirect
- go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
- go.opentelemetry.io/otel/trace v1.37.0 // indirect
- go.uber.org/multierr v1.11.0 // indirect
- go4.org v0.0.0-20230225012048-214862532bf5 // indirect
- golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
- golang.org/x/mod v0.29.0 // indirect
- golang.org/x/sys v0.38.0 // indirect
- golang.org/x/term v0.37.0 // indirect
- google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
- google.golang.org/grpc v1.74.2 // indirect
- gopkg.in/warnings.v0 v0.1.2 // indirect
- pault.ag/go/topsort v0.1.1 // indirect
-)
diff --git a/go.sum b/go.sum
deleted file mode 100644
index 867aee80d8e9..000000000000
--- a/go.sum
+++ /dev/null
@@ -1,1133 +0,0 @@
-cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
-cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
-cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
-cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
-cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
-cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
-cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
-cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
-cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
-cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
-cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
-cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
-cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
-cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
-cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/secretmanager v1.15.0 h1:RtkCMgTpaBMbzozcRUGfZe46jb9a3qh5EdEtVRUATF8=
-cloud.google.com/go/secretmanager v1.15.0/go.mod h1:1hQSAhKK7FldiYw//wbR/XPfPc08eQ81oBsnRUHEvUc=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.56.1 h1:n6gy+yLnHn0hTwBFzNn8zJ1kqWfR91wzdM8hjRF4wP0=
-cloud.google.com/go/storage v1.56.1/go.mod h1:C9xuCZgFl3buo2HZU/1FncgvvOgTAs/rnh4gF4lMg0s=
-cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
-cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
-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=
-filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
-filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
-github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
-github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
-github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
-github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
-github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
-github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
-github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
-github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
-github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
-github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/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=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
-github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=
-github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
-github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
-github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
-github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
-github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
-github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
-github.com/TheZeroSlave/zapsentry v1.23.0 h1:TKyzfEL7LRlRr+7AvkukVLZ+jZPC++ebCUv7ZJHl1AU=
-github.com/TheZeroSlave/zapsentry v1.23.0/go.mod h1:3DRFLu4gIpnCTD4V9HMCBSaqYP8gYU7mZickrs2/rIY=
-github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
-github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
-github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
-github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
-github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
-github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
-github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
-github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
-github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
-github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
-github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
-github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
-github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
-github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
-github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
-github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
-github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
-github.com/avast/apkparser v0.0.0-20250626104540-d53391f4d69d h1:PGSn2pnK/u5ZBompy83R6Wo4BqLYp3dX43QWDoPv7TA=
-github.com/avast/apkparser v0.0.0-20250626104540-d53391f4d69d/go.mod h1:3F9A8btIerUcuy7Fmno+g/nIk4ELKJ6NCs2/KK1bvLs=
-github.com/aws/aws-sdk-go-v2 v1.39.0 h1:xm5WV/2L4emMRmMjHFykqiA4M/ra0DJVSWUkDyBjbg4=
-github.com/aws/aws-sdk-go-v2 v1.39.0/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=
-github.com/aws/aws-sdk-go-v2/config v1.31.7 h1:zS1O6hr6t0nZdBCMFc/c9OyZFyLhXhf/B2IZ9Y0lRQE=
-github.com/aws/aws-sdk-go-v2/config v1.31.7/go.mod h1:GpHmi1PQDdL5pP4JaB00pU0ek4EXVcYH7IkjkUadQmM=
-github.com/aws/aws-sdk-go-v2/credentials v1.18.11 h1:1Fnb+7Dk96/VYx/uYfzk5sU2V0b0y2RWZROiMZCN/Io=
-github.com/aws/aws-sdk-go-v2/credentials v1.18.11/go.mod h1:iuvn9v10dkxU4sDgtTXGWY0MrtkEcmkUmjv4clxhuTc=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 h1:Is2tPmieqGS2edBnmOJIbdvOA6Op+rRpaYR60iBAwXM=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7/go.mod h1:F1i5V5421EGci570yABvpIXgRIBPb5JM+lSkHF6Dq5w=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.5 h1:fSuJX/VBJKufwJG/szWgUdRJVyRiEQDDXNh/6NPrTBg=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.5/go.mod h1:LvN0noQuST+3Su55Wl++BkITpptnfN9g6Ohkv4zs9To=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 h1:UCxq0X9O3xrlENdKf1r9eRJoKz/b0AfGkpp3a7FPlhg=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7/go.mod h1:rHRoJUNUASj5Z/0eqI4w32vKvC7atoWR0jC+IkmVH8k=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 h1:Y6DTZUn7ZUC4th9FMBbo8LVE+1fyq3ofw+tRwkUd3PY=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7/go.mod h1:x3XE6vMnU9QvHN/Wrx2s44kwzV2o2g5x/siw4ZUJ9g8=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
-github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 h1:BszAktdUo2xlzmYHjWMq70DqJ7cROM8iBd3f6hrpuMQ=
-github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7/go.mod h1:XJ1yHki/P7ZPuG4fd3f0Pg/dSGA2cTQBCLw82MH2H48=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 h1:zmZ8qvtE9chfhBPuKB2aQFxW5F/rpwXUgmcVCgQzqRw=
-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7/go.mod h1:vVYfbpd2l+pKqlSIDIOgouxNsGu5il9uDp0ooWb0jys=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 h1:mLgc5QIgOy26qyh5bvW+nDoAppxgn3J2WV3m9ewq7+8=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7/go.mod h1:wXb/eQnqt8mDQIQTTmcw58B5mYGxzLGZGK8PWNFZ0BA=
-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 h1:u3VbDKUCWarWiU+aIUK4gjTr/wQFXV17y3hgNno9fcA=
-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7/go.mod h1:/OuMQwhSyRapYxq6ZNpPer8juGNrB4P5Oz8bZ2cgjQE=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.88.0 h1:k5JXPr+2SrPDwM3PdygZUenn0lVPLa3KOs7cCYqinFs=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.88.0/go.mod h1:xajPTguLoeQMAOE44AAP2RQoUhF8ey1g5IFHARv71po=
-github.com/aws/aws-sdk-go-v2/service/sns v1.38.2 h1:Djc2m7mTPuizL1iMxJfMc209PDy2AqiN1AXrtq/rBdY=
-github.com/aws/aws-sdk-go-v2/service/sns v1.38.2/go.mod h1:kHMCS+JDWKuKSDP9J/v3dlV2S9zNBKbXzaLy/kHSdEE=
-github.com/aws/aws-sdk-go-v2/service/sso v1.29.2 h1:rcoTaYOhGE/zfxE1uR6X5fvj+uKkqeCNRE0rBbiQM34=
-github.com/aws/aws-sdk-go-v2/service/sso v1.29.2/go.mod h1:Ql6jE9kyyWI5JHn+61UT/Y5Z0oyVJGmgmJbZD5g4unY=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.3 h1:BSIfeFtU9tlSt8vEYS7KzurMoAuYzYPWhcZiMtxVf2M=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.3/go.mod h1:XclEty74bsGBCr1s0VSaA11hQ4ZidK4viWK7rRfO88I=
-github.com/aws/aws-sdk-go-v2/service/sts v1.38.3 h1:yEiZ0ztgji2GsCb/6uQSITXcGdtmWMfLRys0jJFiUkc=
-github.com/aws/aws-sdk-go-v2/service/sts v1.38.3/go.mod h1:Z+Gd23v97pX9zK97+tX4ppAgqCt3Z2dIXB02CtBncK8=
-github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
-github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
-github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E=
-github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
-github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
-github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
-github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
-github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
-github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
-github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
-github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
-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/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
-github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
-github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
-github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=
-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.16.0 h1:B91r9bHtXp/+XRgS5aZm6ZzTdz3ahgJYmkt4xZkgDz8=
-github.com/bradleyfalzon/ghinstallation/v2 v2.16.0/go.mod h1:OeVe5ggFzoBnmgitZe/A+BqGOnv1DvU/0uiLQi1wutM=
-github.com/brianvoe/gofakeit/v7 v7.6.0 h1:M3RUb5CuS2IZmF/cP+O+NdLxJEuDAZxNQBwPbbqR6h4=
-github.com/brianvoe/gofakeit/v7 v7.6.0/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
-github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
-github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
-github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
-github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
-github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
-github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
-github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
-github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
-github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=
-github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=
-github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
-github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
-github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
-github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
-github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
-github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
-github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
-github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
-github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
-github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
-github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
-github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
-github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
-github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
-github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
-github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
-github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
-github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
-github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
-github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
-github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
-github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
-github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
-github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
-github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
-github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
-github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
-github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
-github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
-github.com/couchbase/gocb/v2 v2.11.0 h1:OVB+KlVeXlKVtziKx/LWZT7DClLsoQHQFrI4wan5Ijc=
-github.com/couchbase/gocb/v2 v2.11.0/go.mod h1:Y+lODSgyVzDSaf0Sy8sIzIa0RTAw3vlZUsjY6+FUq9Y=
-github.com/couchbase/gocbcore/v10 v10.8.0 h1:zDcJyYqOirFyC8T/aVvNL4N9oj6GI4qtaBuTGGWCDb4=
-github.com/couchbase/gocbcore/v10 v10.8.0/go.mod h1:OWKfU9R5Nm5V3QZBtfdZl5qCfgxtxTqOgXiNr4pn9/c=
-github.com/couchbase/gocbcoreps v0.1.3 h1:fILaKGCjxFIeCgAUG8FGmRDSpdrRggohOMKEgO9CUpg=
-github.com/couchbase/gocbcoreps v0.1.3/go.mod h1:hBFpDNPnRno6HH5cRXExhqXYRmTsFJlFHQx7vztcXPk=
-github.com/couchbase/goprotostellar v1.0.2 h1:yoPbAL9sCtcyZ5e/DcU5PRMOEFaJrF9awXYu3VPfGls=
-github.com/couchbase/goprotostellar v1.0.2/go.mod h1:5/yqVnZlW2/NSbAWu1hPJCFBEwjxgpe0PFFOlRixnp4=
-github.com/couchbaselabs/gocaves/client v0.0.0-20250107114554-f96479220ae8 h1:MQfvw4BiLTuyR69FuA5Kex+tXUeLkH+/ucJfVL1/hkM=
-github.com/couchbaselabs/gocaves/client v0.0.0-20250107114554-f96479220ae8/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY=
-github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20240607131231-fb385523de28 h1:lhGOw8rNG6RAadmmaJAF3PJ7MNt7rFuWG7BHCYMgnGE=
-github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20240607131231-fb385523de28/go.mod h1:o7T431UOfFVHDNvMBUmUxpHnhivwv7BziUao/nMl81E=
-github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
-github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
-github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
-github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
-github.com/crewjam/rfc5424 v0.1.0 h1:MSeXJm22oKovLzWj44AHwaItjIMUMugYGkEzfa831H8=
-github.com/crewjam/rfc5424 v0.1.0/go.mod h1:RCi9M3xHVOeerf6ULZzqv2xOGRO/zYaVUeRyPnBW3gQ=
-github.com/csnewman/dextk v0.3.0 h1:gigNZlZRNfCuARV7depunRlafEAzGhyvgBQo1FT3/0M=
-github.com/csnewman/dextk v0.3.0/go.mod h1:FcDoI3258ea0KPQogyv4iazQRGcLFNOW+I4pHBUfNO0=
-github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
-github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
-github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
-github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
-github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
-github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
-github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
-github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A=
-github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
-github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
-github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
-github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
-github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
-github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
-github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
-github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
-github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
-github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
-github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
-github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
-github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
-github.com/elastic/elastic-transport-go/v8 v8.6.1 h1:h2jQRqH6eLGiBSN4eZbQnJLtL4bC5b4lfVFRjw2R4e4=
-github.com/elastic/elastic-transport-go/v8 v8.6.1/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
-github.com/elastic/go-elasticsearch/v8 v8.17.1 h1:bOXChDoCMB4TIwwGqKd031U8OXssmWLT3UrAr9EGs3Q=
-github.com/elastic/go-elasticsearch/v8 v8.17.1/go.mod h1:MVJCtL+gJJ7x5jFeUmA20O7rvipX8GcQmo5iBcmaJn4=
-github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM=
-github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
-github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
-github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
-github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
-github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
-github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
-github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
-github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
-github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
-github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
-github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
-github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
-github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
-github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
-github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
-github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
-github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY=
-github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
-github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
-github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
-github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
-github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
-github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
-github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
-github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
-github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
-github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
-github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
-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.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=
-github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
-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-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
-github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
-github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
-github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
-github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
-github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
-github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
-github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
-github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
-github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
-github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
-github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
-github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
-github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
-github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
-github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
-github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
-github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
-github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
-github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
-github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
-github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
-github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
-github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
-github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
-github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
-github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
-github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
-github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
-github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
-github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
-github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
-github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
-github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
-github.com/google/go-github/v67 v67.0.0 h1:g11NDAmfaBaCO8qYdI9fsmbaRipHNWRIU/2YGvlh4rg=
-github.com/google/go-github/v67 v67.0.0/go.mod h1:zH3K7BxjFndr9QSeFibx4lTKkYS3K9nDanoI1NjaOtY=
-github.com/google/go-github/v72 v72.0.0 h1:FcIO37BLoVPBO9igQQ6tStsv2asG4IPcYFi655PPvBM=
-github.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg=
-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=
-github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
-github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
-github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
-github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
-github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
-github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
-github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
-github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
-github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
-github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
-github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
-github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
-github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
-github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
-github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
-github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
-github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
-github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
-github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
-github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
-github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
-github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
-github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
-github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
-github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
-github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
-github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
-github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
-github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
-github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
-github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=
-github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
-github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
-github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
-github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
-github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
-github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
-github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
-github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
-github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
-github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
-github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
-github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
-github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
-github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
-github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
-github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
-github.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc=
-github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
-github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
-github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
-github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
-github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
-github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
-github.com/jpillora/s3 v1.1.4 h1:YCCKDWzb/Ye9EBNd83ATRF/8wPEy0xd43Rezb6u6fzc=
-github.com/jpillora/s3 v1.1.4/go.mod h1:yedE603V+crlFi1Kl/5vZJaBu9pUzE9wvKegU/lF2zs=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
-github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
-github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
-github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d h1:RnWZeH8N8KXfbwMTex/KKMYMj0FJRCF6tQubUuQ02GM=
-github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q=
-github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
-github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
-github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
-github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
-github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
-github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
-github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
-github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
-github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
-github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
-github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
-github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo=
-github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=
-github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=
-github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
-github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
-github.com/lestrrat-go/httprc/v3 v3.0.1 h1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI=
-github.com/lestrrat-go/httprc/v3 v3.0.1/go.mod h1:2uAvmbXE4Xq8kAUjVrZOq1tZVYYYs5iP62Cmtru00xk=
-github.com/lestrrat-go/jwx/v3 v3.0.12 h1:p25r68Y4KrbBdYjIsQweYxq794CtGCzcrc5dGzJIRjg=
-github.com/lestrrat-go/jwx/v3 v3.0.12/go.mod h1:HiUSaNmMLXgZ08OmGBaPVvoZQgJVOQphSrGr5zMamS8=
-github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
-github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
-github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
-github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
-github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
-github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lrstanley/bubblezone v0.0.0-20250404061050-e13639e27357 h1:DxFncLGTrDh5v0z+DE7h+qjD5tPCGR+3knGVVfT3YLI=
-github.com/lrstanley/bubblezone v0.0.0-20250404061050-e13639e27357/go.mod h1:S5etECMx+sZnW0Gm100Ma9J1PgVCTgNyFaqGu2b08b4=
-github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
-github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
-github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
-github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
-github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
-github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
-github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM=
-github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ=
-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
-github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
-github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
-github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
-github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d h1:Vw3f39TqFSQLA+OyW+8SouppHTYzX8/fDv6Ao8uj3Ho=
-github.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I=
-github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
-github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
-github.com/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I=
-github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
-github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
-github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
-github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
-github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
-github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
-github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
-github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
-github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
-github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
-github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
-github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
-github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
-github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
-github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
-github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
-github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
-github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
-github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
-github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
-github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
-github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
-github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
-github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
-github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
-github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
-github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
-github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
-github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
-github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
-github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
-github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
-github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
-github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/nwaples/rardecode/v2 v2.2.1 h1:DgHK/O/fkTQEKBJxBMC5d9IU8IgauifbpG78+rZJMnI=
-github.com/nwaples/rardecode/v2 v2.2.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
-github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
-github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
-github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
-github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
-github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
-github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
-github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
-github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
-github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
-github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
-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.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
-github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
-github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
-github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
-github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
-github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
-github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
-github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
-github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
-github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
-github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
-github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
-github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
-github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
-github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
-github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
-github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
-github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
-github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
-github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
-github.com/repeale/fp-go v0.11.1 h1:Q/e+gNyyHaxKAyfdbBqvip3DxhVWH453R+kthvSr9Mk=
-github.com/repeale/fp-go v0.11.1/go.mod h1:4KrwQJB1VRY+06CA+jTc4baZetr6o2PeuqnKr5ybQUc=
-github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
-github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
-github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
-github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
-github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
-github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
-github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
-github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
-github.com/schollz/progressbar/v3 v3.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U=
-github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4=
-github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
-github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
-github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
-github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
-github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs=
-github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
-github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
-github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
-github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
-github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
-github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
-github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
-github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
-github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
-github.com/shuheiktgw/go-travis v0.3.1 h1:SAT16mi77ccqogOslnXxBXzXbpeyChaIYUwi2aJpVZY=
-github.com/shuheiktgw/go-travis v0.3.1/go.mod h1:avnFFDqJDdRHwlF9tgqvYi3asQCm/HGL8aLxYiKa4Yg=
-github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 h1:cYCy18SHPKRkvclm+pWm1Lk4YrREb4IOIb/YdFO0p2M=
-github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8=
-github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=
-github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
-github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
-github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
-github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
-github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
-github.com/smartystreets/gunit v1.1.3 h1:32x+htJCu3aMswhPw3teoJ+PnWPONqdNgaGs6Qt8ZaU=
-github.com/smartystreets/gunit v1.1.3/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
-github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=
-github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=
-github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
-github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
-github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo=
-github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ=
-github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.34.0 h1:BBwJUs9xBpt1uOfO+yAr2pYW75MsyzuO/o70HTPnhe4=
-github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.34.0/go.mod h1:OqhRGYR+5VG0Dw506F6Ho9I4YG1kB+o9uPTKC0uPUA8=
-github.com/testcontainers/testcontainers-go/modules/mongodb v0.34.0 h1:o3bgcECyBFfMwqexCH/6vIJ8XzbCffCP/Euesu33rgY=
-github.com/testcontainers/testcontainers-go/modules/mongodb v0.34.0/go.mod h1:ljLR42dN7k40CX0dp30R8BRIB3OOdvr7rBANEpfmMs4=
-github.com/testcontainers/testcontainers-go/modules/mssql v0.34.0 h1:4Pf7ILuLnxhpeTgQfKzEMPuMQhasU3VaYer9l5HrQ3s=
-github.com/testcontainers/testcontainers-go/modules/mssql v0.34.0/go.mod h1:L2eMWZ49X0XjewabzJ6TXSY5z4SAWM/2WOBqlIxYFD8=
-github.com/testcontainers/testcontainers-go/modules/mysql v0.34.0 h1:Tqz17mGXjPORHFS/oBUGdeJyIsZXLsVVHRhaBqhewGI=
-github.com/testcontainers/testcontainers-go/modules/mysql v0.34.0/go.mod h1:hDpm3DLfjo7rd6232wWflEBDGr6Ow9ys43mJTiJwWx8=
-github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0 h1:c51aBXT3v2HEBVarmaBnsKzvgZjC5amn0qsj8Naqi50=
-github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0/go.mod h1:EWP75ogLQU4M4L8U+20mFipjV4WIR9WtlMXSB6/wiuc=
-github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
-github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
-github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
-github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
-github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
-github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
-github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
-github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
-github.com/trufflesecurity/disk-buffer-reader v0.2.1 h1:K9nNpX3xeWT2E6YRjlcc1X5c1NjgV9JS5T9aw2FjA8Q=
-github.com/trufflesecurity/disk-buffer-reader v0.2.1/go.mod h1:uYwTCdxzV0o+qaeBMxflOsq4eu2WjrE46qGR2e80O9Y=
-github.com/trufflesecurity/overseer v1.2.8 h1:VXlWPiwYaQmwNxY2W1rVulEAG9O6iF1S0LX3wionWYM=
-github.com/trufflesecurity/overseer v1.2.8/go.mod h1:Dt6Y9LFpM+C/3rRWpy4//4iS5qrbb0pL3XvZqMd4zhg=
-github.com/trufflesecurity/touchfile v0.1.1 h1:Snhd5VEa8Cxd+D60nvLEj2kVeb1omY2tWwnhDhjTqdo=
-github.com/trufflesecurity/touchfile v0.1.1/go.mod h1:Yg/AUMrxAk+dWDUjIig0OyGgFOHFuWNw+t2S/GvO6Mk=
-github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
-github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
-github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
-github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
-github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
-github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
-github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
-github.com/wasilibs/go-re2 v1.9.0 h1:kjAd8qbNvV4Ve2Uf+zrpTCrDHtqH4dlsRXktywo73JQ=
-github.com/wasilibs/go-re2 v1.9.0/go.mod h1:0sRtscWgpUdNA137bmr1IUgrRX0Su4dcn9AEe61y+yI=
-github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4=
-github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY=
-github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
-github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
-github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
-github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
-github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
-github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
-github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
-github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
-github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
-github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
-github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
-github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
-github.com/xo/dburl v0.23.8 h1:NwFghJfjaUW7tp+WE5mTLQQCfgseRsvgXjlSvk7x4t4=
-github.com/xo/dburl v0.23.8/go.mod h1:uazlaAQxj4gkshhfuuYyvwCBouOmNnG2aDxTCFZpmL4=
-github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
-github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
-github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
-github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
-github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
-github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
-github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
-github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
-github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
-github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
-github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
-github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
-github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
-github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
-gitlab.com/gitlab-org/api/client-go v0.129.0 h1:o9KLn6fezmxBQWYnQrnilwyuOjlx4206KP0bUn3HuBE=
-gitlab.com/gitlab-org/api/client-go v0.129.0/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM=
-go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
-go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
-go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
-go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
-go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
-go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
-go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
-go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
-go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
-go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
-go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
-go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
-go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
-go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
-go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
-go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
-go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
-golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
-golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
-golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
-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=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
-golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
-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=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
-golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
-golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
-golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
-golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
-golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
-golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
-golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
-golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
-golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
-golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
-golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
-golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
-golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
-golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
-golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
-golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=
-google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
-google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
-google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ=
-google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
-google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
-google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
-gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
-gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
-gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-pault.ag/go/debian v0.18.0 h1:nr0iiyOU5QlG1VPnhZLNhnCcHx58kukvBJp+dvaM6CQ=
-pault.ag/go/debian v0.18.0/go.mod h1:JFl0XWRCv9hWBrB5MDDZjA5GSEs1X3zcFK/9kCNIUmE=
-pault.ag/go/topsort v0.1.1 h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4=
-pault.ag/go/topsort v0.1.1/go.mod h1:r1kc/L0/FZ3HhjezBIPaNVhkqv8L0UJ9bxRuHRVZ0q4=
-pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
-pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
-sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff --git a/hack/Dockerfile.protos b/hack/Dockerfile.protos
deleted file mode 100644
index ad9cb8cd8ef8..000000000000
--- a/hack/Dockerfile.protos
+++ /dev/null
@@ -1,35 +0,0 @@
-# trufflesecurity/protos:1.23
-
-FROM golang:1.24-bullseye
-
-ARG TARGETARCH
-ARG TARGETOS
-
-ENV PROTOC_VER=25.3
-ENV PROTOC_GEN_GO_VER=v1.5.4
-ENV PROTOC_GEN_VALIDATE_VER=v1.0.4
-ENV GORELEASER_VERSION=1.19.2
-
-RUN echo "building $TARGETARCH"
-RUN go install github.com/dustin-decker/quill/cmd/quill@v0.5.1
-RUN apt-get update; apt-get install apt-transport-https ca-certificates curl gnupg lsb-release -y; \
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg; \
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
- $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null; \
- apt-get update; apt-get install -y --no-install-recommends python3-pip docker-ce-cli docker-buildx-plugin docker-compose-plugin \
- git netbase wget upx unzip && rm -rf /var/lib/apt/lists/*
-RUN pip3 install --upgrade setuptools pip
-RUN set -e; \
- arch=$(echo $TARGETARCH | sed -e s/amd64/x86_64/ -e s/arm64/aarch_64/); \
- wget -q https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-${TARGETOS}-${arch}.zip && unzip protoc-${PROTOC_VER}-${TARGETOS}-${arch}.zip -d /usr/local
-RUN curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -; echo "deb https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list; apt-get update; apt-get install -y google-cloud-cli
-RUN echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | tee /etc/apt/sources.list.d/goreleaser.list; \
- apt-get update; apt-get install -y goreleaser=${GORELEASER_VERSION} && rm -rf /var/lib/apt/lists/*
-RUN go install "github.com/golang/protobuf/protoc-gen-go@${PROTOC_GEN_GO_VER}"
-RUN go install gotest.tools/gotestsum@latest
-RUN git clone https://github.com/envoyproxy/protoc-gen-validate $GOPATH/src/github.com/envoyproxy/protoc-gen-validate && \
- cd $GOPATH/src/github.com/envoyproxy/protoc-gen-validate && \
- git checkout ${PROTOC_GEN_VALIDATE_VER} && \
- ln -s /usr/local/protoc/include/google google && \
- make build
-CMD ["bash"]
diff --git a/hack/bench/plot.gp b/hack/bench/plot.gp
deleted file mode 100644
index 28da33d70271..000000000000
--- a/hack/bench/plot.gp
+++ /dev/null
@@ -1,10 +0,0 @@
-set terminal png size 800,600
-set output "hack/bench/versions.png"
-
-set title "User Time vs. Version"
-set xlabel "Version"
-set ylabel "Average User Time (s)"
-
-set xtics rotate by -45
-
-plot "hack/bench/plot.txt" using 2:xtic(1) with linespoints linestyle 1 notitle
diff --git a/hack/bench/plot.sh b/hack/bench/plot.sh
deleted file mode 100755
index 6ef222a59a7e..000000000000
--- a/hack/bench/plot.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-
-if [ $# -ne 2 ]; then
- echo "Usage: $0 "
- exit 1
-fi
-
-# Get the number of versions back to test from command line argument
-num_versions="$2"
-
-test_repo="$1"
-
-bash hack/bench/versions.sh $test_repo $num_versions | tee hack/bench/plot.txt
-
-gnuplot hack/bench/plot.gp
diff --git a/hack/bench/plot.txt b/hack/bench/plot.txt
deleted file mode 100644
index 51f5c750f5df..000000000000
--- a/hack/bench/plot.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-v3.33.0: 1.402
-v3.32.2: 1.298
-v3.32.1: 1.332
-v3.32.0: 1.348
-v3.31.6: 2.470
-v3.31.5: 2.462
-v3.31.4: 2.460
-v3.31.3: 2.418
-v3.31.2: 1.384
-v3.31.1: 1.344
-v3.31.0: 1.354
-v3.30.0: 1.392
-v3.29.1: 1.382
-v3.29.0: 1.340
-v3.28.7: 1.380
-v3.28.6: 1.308
-v3.28.5: 2.596
-v3.28.4: 2.554
-v3.28.3: 2.582
-v3.28.1: 2.578
-v3.28.2: 2.566
-v3.28.0: 2.552
-v3.27.1: 2.574
-v3.26.0: 2.538
diff --git a/hack/bench/versions.png b/hack/bench/versions.png
deleted file mode 100644
index 8618e081eac6..000000000000
Binary files a/hack/bench/versions.png and /dev/null differ
diff --git a/hack/bench/versions.sh b/hack/bench/versions.sh
deleted file mode 100755
index c633a74cc7f6..000000000000
--- a/hack/bench/versions.sh
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/bin/bash
-
-if [ $# -ne 2 ]; then
- echo "Usage: $0 "
- exit 1
-fi
-
-# Get the number of versions back to test from command line argument
-num_versions="$2"
-
-test_repo="$1"
-
-num_iterations=5
-
-# Create a temporary folder to clone the repository
-repo_tmp=$(mktemp -d)
-# Set up a trap to remove the temporary folder on exit or failure
-trap "rm -rf $repo_tmp" EXIT
-# Clone the test repository to a temporary folder
-git clone --quiet "$test_repo" $repo_tmp
-
-
-# Get list of git tags, sorted from newest to oldest
-tags=$(echo $(git describe --tags --always --dirty --match='v*') $(git tag --sort=-creatordate))
-
-# Counter to keep track of number of tags checked out
-count=0
-
-
-# Loop over tags and checkout each one in turn, up to the specified number of versions
-for tag in $tags
-do
- if [[ $count -eq $num_versions ]]; then
- break
- fi
-
- # Skip RC tags
- if [[ $tag == *"rc"* ]]; then
- continue
- fi
-
- # Skip alpha tags
- if [[ $tag == *"alpha"* ]]; then
- continue
- fi
-
- # Use git checkout with the quiet flag to suppress output
- git checkout $tag --quiet
-
- # Run make install with suppressed output
- make install > /dev/null
-
- # Initialize the variable to store the sum of user times
- user_time_sum=0
-
- # Run each iteration 5 times and calculate the average user time
- for i in {1..$num_iterations}
- do
- # Run trufflehog with suppressed output and capture user time with /usr/bin/time
- tmpfile=$(mktemp)
- /usr/bin/time -o $tmpfile trufflehog git "file://$repo_tmp" --no-verification --no-update >/dev/null 2>&1
- time_output=$(cat $tmpfile)
- rm $tmpfile
-
- # Extract the user time from the output
- user_time=$(echo $time_output | awk '{print $3}')
-
- # Add the user time to the sum
- user_time_sum=$(echo "$user_time_sum + $user_time" | bc)
- done
-
- # Calculate the average user time
- average_user_time=$(echo "scale=3; $user_time_sum / $num_iterations" | bc)
-
- # Print the average user time output for this iteration in the specified format
- echo "$tag: $average_user_time"
-
- # Increment the counter
- count=$((count+1))
-done
diff --git a/hack/docs/Adding_Detectors_Internal.md b/hack/docs/Adding_Detectors_Internal.md
deleted file mode 100644
index cc0caacd5741..000000000000
--- a/hack/docs/Adding_Detectors_Internal.md
+++ /dev/null
@@ -1,164 +0,0 @@
-# Secret Detectors
-
-Secret Detectors have these two major functions:
-
-1. Given some bytes, extract possible secrets, typically using a regex.
-2. Validate the secrets against the target API, typically using a HTTP client.
-
-The purpose of Secret Detectors is to discover secrets with exceptionally high signal. High rates of false positives are not accepted.
-
-## Table of Contents
-
-- [Secret Detectors](#secret-detectors)
- - [Table of Contents](#table-of-contents)
- - [Getting Started](#getting-started)
- - [Sourcing Guidelines](#sourcing-guidelines)
- - [Development Guidelines](#development-guidelines)
- - [Development Dependencies](#development-dependencies)
- - [Creating a new Secret Scanner](#creating-a-new-secret-detector)
- - [Addendum](#addendum)
- - [Managing Test Secrets](#managing-test-secrets)
- - [Setting up Google Cloud SDK](#setting-up-google-cloud-sdk)
-
-## Getting Started
-
-### Sourcing Guidelines
-
-We are interested in detectors for services that meet at least one of these criteria
-- host data (they store any sort of data provided)
-- have paid services (having a free or trial tier is okay though)
-
-If you think that something should be included outside of these guidelines, please let us know.
-
-### Development Guidelines
-
-- When reasonable, favor using the `net/http` library to make requests instead of bringing in another library.
-- Use the [`common.SaneHttpClient`](pkg/common/http.go) for the `http.Client` whenever possible.
-- We recommend an editor with gopls integration (such as Vscode with Go plugin) for benefits like easily running tests, autocompletion, linting, type checking, etc.
-
-### Development Dependencies
-
-- A GitLab account
-- A Google account
-- [Google Cloud SDK installed](#setting-up-google-cloud-sdk)
-- Go 1.17+
-- Make
-
-### Adding New Token Formats to an Existing Scanner
-
-In some instances, services will update their token format, requiring a new regex to properly detect secrets in addition to supporting the previous token format. Accommodating this can be done without adding a net-new detector. [We provide a `Versioner` interface](https://github.com/trufflesecurity/trufflehog/blob/e18cfd5e0af1469a9f05b8d5732bcc94c39da49c/pkg/detectors/detectors.go#L30) that can be implemented.
-
-1. Create two new directories `v1` and `v2`. Move the existing detector and tests into `v1`, and add new files to `v2`.
-Ex: `/` -> `/v1/`, `/v2/`
-
-Note: Be sure to update the tests to reference the new secret values in GSM, or the tests will fail.
-
-2. Implement the `Versioner` interface. [GitHub example implementation.](/pkg/detectors/github/v1/github_old.go#L23)
-
-3. Add a 'version' field in ExtraData for both existing and new detector versions.
-
-4. Update the existing detector in DefaultDetectors in `/pkg/engine/defaults/defaults.go`
-
-5. Proceed from step 3 of [Creating a new Secret Scanner](#creating-a-new-secret-scanner)
-
-### Creating a new Secret Scanner
-
-1. Identify the Secret Detector name from the [/proto/detectors.proto](/proto/detectors.proto) `DetectorType` enum.
-
-2. Generate the Secret Detector
-
- ```bash
- go run hack/generate/generate.go detector
- ```
-
-3. Complete the secret detector.
-
- The previous step templated a boilerplate + some example code as a package in the `pkg/detectors` folder for you to work on.
- The secret detector can be completed with these general steps:
-
- 1. Add the test secret to GCP Secrets. See [managing test secrets](#managing-test-secrets)
- 2. Update the pattern regex and keywords. Try iterating with [regex101.com](http://regex101.com/).
- 3. Update the verifier code to use a non-destructive API call that can determine whether the secret is valid or not.
- * Make sure you understand [verification indeterminacy](#verification-indeterminacy).
- 4. Update the tests with these test cases at minimum:
- 1. Found and verified (using a credential loaded from GCP Secrets)
- 2. Found and unverified (determinately, i.e. the secret is invalid)
- 3. Found and unverified (indeterminately due to timeout)
- 4. Found and unverified (indeterminately due to an unexpected API response)
- 5. Not found
- 6. Any false positive cases that you come across
- 5. Add your new detector to DefaultDetectors in `/pkg/engine/defaults/defaults.go`
- 6. Create a merge request for review. CI tests must be passing.
-
-## Addendum
-
-### Verification indeterminacy
-
-There are two types of reasons that secret verification can fail:
-* The candidate secret is not actually a valid secret.
-* Something went wrong in the process unrelated to the candidate secret, such as a transient network error or an unexpected API response.
-
-In TruffleHog parlance, the first type of verification response is called _determinate_ and the second type is called _indeterminate_. Verification code should distinguish between the two by returning an error object in the result struct **only** for indeterminate failures. In general, a verifier should return an error (indicating an indeterminate failure) in all cases that haven't been explicitly identified as determinate failure states.
-
-For example, consider a hypothetical authentication endpoint that returns `200 OK` for valid credentials and `403 Forbidden` for invalid credentials. The verifier for this endpoint could make an HTTP request and use the response status code to decide what to return:
-* A `200` response would indicate that verification succeeded. (Or maybe any `2xx` response.)
-* A `403` response would indicate that verification failed **determinately** and no error object should be returned.
-* Any other response would indicate that verification failed **indeterminately** and an error object should be returned.
-
-### Managing Test Secrets
-
-Do not embed test credentials in the test code. Instead, use GCP Secrets Manager.
-
-1. Access the latest secret version for modification.
-
- Note: `/tmp/s` is a valid path on Linux. You will need to change that for Windows or OSX, otherwise you will see an error. On Windows you will also need to install [WSL](https://docs.microsoft.com/en-us/windows/wsl/install).
-
- ```bash
- gcloud secrets versions access --project trufflehog-testing --secret detectors5 latest > /tmp/s
- ```
-
-2. Add the secret that you need for testing.
-
- The command above saved it to `/tmp/s`.
-
- The format is standard env file format,
-
- ```bash
- SECRET_TYPE_ONE=value
- SECRET_TYPE_ONE_INACTIVE=v@lue
- ```
-
-3. Update the secret version with your modification.
-
- ```bash
- gcloud secrets versions add --project trufflehog-testing detectors5 --data-file /tmp/s
- ```
- Note: We increment the detectors file name `detectors(n+1)` once the previous one exceeds the max size allowed by GSM (65kb).
-
-4. Access the secret value as shown in the [example code](pkg/detectors/heroku/heroku_test.go).
-
-### Setting up Google Cloud SDK
-
-1. Install the Google Cloud SDK: https://cloud.google.com/sdk/docs/install
-2. Authenticate with `gcloud auth login --update-adc` using your Google account
-
-### Adding Protos in Windows
-
-1. Install Ubuntu App in Microsoft Store https://www.microsoft.com/en-us/p/ubuntu/9nblggh4msv6.
-2. Install Docker Desktop https://www.docker.com/products/docker-desktop. Enable WSL integration to Ubuntu. In Docker app, go to Settings->Resources->WSL INTEGRATION->enable Ubuntu.
-3. Open Ubuntu cli and install `dos2unix`.
- ```bash
- sudo apt install dos2unix
- ```
-4. Identify the `trufflehog` local directory and convert `scripts/gen_proto.sh` file in Unix format.
- ```bash
- dos2unix ./scripts/gen_proto.sh
- ```
-5. Open [/proto/detectors.proto](/proto/detectors.proto) file and add new detectors then save it. Make sure Docker is running and run this in Ubuntu command line.
- ```bash
- make protos
- ```
-### Testing a detector
-```bash
- go test ./pkg/detectors/ -tags=detectors
- ```
diff --git a/hack/docs/Adding_Detectors_external.md b/hack/docs/Adding_Detectors_external.md
deleted file mode 100644
index e864e41e4f4a..000000000000
--- a/hack/docs/Adding_Detectors_external.md
+++ /dev/null
@@ -1,164 +0,0 @@
-# Secret Detectors
-
-Secret Detectors have these two major functions:
-
-1. Given some bytes, extract possible secrets, typically using a regex.
-2. Validate the secrets against the target API, typically using a HTTP client.
-
-The purpose of Secret Detectors is to discover secrets with exceptionally high signal. High rates of false positives are not accepted.
-
-## Table of Contents
-
-- [Secret Detectors](#secret-detectors)
- * [Table of Contents](#table-of-contents)
- * [Getting Started](#getting-started)
- + [Sourcing Guidelines](#sourcing-guidelines)
- + [Development Guidelines](#development-guidelines)
- + [Development Dependencies](#development-dependencies)
- + [Creating a new Secret Detector](#creating-a-new-secret-detector)
- + [Testing the Detector](#testing-the-detector)
- * [Addendum](#addendum)
- + [Adding Protos in Windows](#adding-protos-in-windows)
-
-## Getting Started
-
-### Sourcing Guidelines
-
-We are interested in detectors for services that meet at least one of these criteria
-- host data (they store any sort of data provided)
-- have paid services (having a free or trial tier is okay though)
-
-If you think that something should be included outside of these guidelines, please let us know.
-
-### Development Guidelines
-
-- When reasonable, favor using the `net/http` library to make requests instead of bringing in another library.
-- Use the [`common.SaneHttpClient`](/pkg/common/http.go) for the `http.Client` whenever possible.
-
-### Development Dependencies
-
-- Go 1.17+
-- Make
-
-### Adding New Token Formats to an Existing Scanner
-
-In some instances, services will update their token format, requiring a new regex to properly detect secrets in addition to supporting the previous token format. Accommodating this can be done without adding a net-new detector. [We provide a `Versioner` interface](https://github.com/trufflesecurity/trufflehog/blob/e18cfd5e0af1469a9f05b8d5732bcc94c39da49c/pkg/detectors/detectors.go#L30) that can be implemented.
-
-1. Create two new directories `v1` and `v2`. Move the existing detector and tests into `v1`, and add new files to `v2`.
-Ex: `/` -> `/v1/`, `/v2/`
-
-Note: Be sure to update the tests to reference the new secret values in GSM, or the tests will fail.
-
-2. Implement the `Versioner` interface. [GitHub example implementation.](https://github.com/trufflesecurity/trufflehog/blob/2964b3b2d2edf2b60b1f71443338c6534720b67a/pkg/detectors/github/v1/github_old.go#L23))
-
-3. Add a 'version' field in ExtraData for both existing and new detector versions.
-
-4. Update the existing detector in DefaultDetectors in `/pkg/engine/defaults/defaults.go`
-
-5. Proceed from step 3 of [Creating a new Secret Scanner](#creating-a-new-secret-scanner)
-
-### Creating a new Secret Detector
-
-1. Add a new Secret Detector enum to the [`DetectorType` list here](/proto/detectors.proto).
-
-2. Run `make protos` to update the `.pb` files.
-
-3. Generate the Secret Detector
-
- ```bash
- go run hack/generate/generate.go detector
- example: go run hack/generate/generate.go detector SampleAPI
- ```
-4. Add the Secret Detector to TruffleHog's Default Detectors
-
- Add the secret scanner to the [`pkg/engine/defaults/defaults.go`](https://github.com/trufflesecurity/trufflehog/blob/main/pkg/engine/defaults/defaults.go) file like [`github.com/trufflesecurity/trufflehog/v3/pkg/detectors/`](https://github.com/trufflesecurity/trufflehog/blob/b71ea27a696bdf1c3141f637fda4ee4936c2f2d6/pkg/engine/defaults/defaults.go#L9) and
- [`.Scanner{}`](https://github.com/trufflesecurity/trufflehog/blob/b71ea27a696bdf1c3141f637fda4ee4936c2f2d6/pkg/engine/defaults/defaults.go#L1546)
-
-5. Complete the Secret Detector.
-
- The previous step templated a boilerplate + some example code as a package in the `pkg/detectors` folder for you to work on.
- The Secret Detector can be completed with these general steps:
-
- 1. Update the pattern regex and keywords. Try iterating with [regex101.com](http://regex101.com/).
- 2. Update the verifier code to use a non-destructive API call that can determine whether the secret is valid or not.
- * Make sure you understand [verification indeterminacy](#verification-indeterminacy).
- 3. Create a [test for the detector](#testing-the-detector).
- 4. Add your new detector to DefaultDetectors in `/pkg/engine/defaults/defaults.go`.
- 5. Create a pull request for review.
-
-### Testing the Detector
-To ensure the quality of your PR, make sure your tests are passing with verified credentials.
-
-1. Create a file called `.env` with this env file format:
-
- ```bash
- SECRET_TYPE_ONE=value
- SECRET_TYPE_ONE_INACTIVE=v@lue
- ```
-
-2. Export the `TEST_SECRET_FILE` variable, pointing to the env file:
-
- ```bash
- export TEST_SECRET_FILE=".env"
- ```
- The `.env` file should be in the new detector's directory like this:
- ```
- ├── tailscale
- │ ├── .env
- │ ├── tailscale.go
- │ └── tailscale_test.go
- ```
- Now that a `.env` file is present, the test file can load secrets locally.
-
-3. Next, update the tests as necessary. A test file has already been generated by the `go run hack/generate/generate.go` command from earlier. There are 5 cases that have been generated:
- 1. Found and verified (using a credential loaded from the .env file)
- 2. Found and unverified (determinately, i.e. the secret is invalid)
- 3. Found and unverified (indeterminately due to timeout)
- 4. Found and unverified (indeterminately due to an unexpected API response)
- 5. Not found
-
- Make any necessary updates to the tests. Note there might not be any changes required as the tests generated by the `go run hack/generate/generate.go` command are pretty good.
- [Here is an exemplary test file for a detector which covers all 5 test cases](https://github.com/trufflesecurity/trufflehog/blob/6f9065b0aae981133a7fa3431c17a5c6213be226/pkg/detectors/browserstack/browserstack_test.go).
-
-4. Now run the tests and check to make sure they are passing ✔️!
-```bash
- go test ./pkg/detectors/ -tags=detectors
- ```
-
-If the tests are passing, feel free to open a PR!
-
-
-
-
-## Addendum
-
-### Verification indeterminacy
-
-There are two types of reasons that secret verification can fail:
-* The candidate secret is not actually a valid secret.
-* Something went wrong in the process unrelated to the candidate secret, such as a transient network error or an unexpected API response.
-
-In TruffleHog parlance, the first type of verification response is called _determinate_ and the second type is called _indeterminate_. Verification code should distinguish between the two by returning an error object in the result struct **only** for indeterminate failures. In general, a verifier should return an error (indicating an indeterminate failure) in all cases that haven't been explicitly identified as determinate failure states.
-
-For example, consider a hypothetical authentication endpoint that returns `200 OK` for valid credentials and `403 Forbidden` for invalid credentials. The verifier for this endpoint could make an HTTP request and use the response status code to decide what to return:
-* A `200` response would indicate that verification succeeded. (Or maybe any `2xx` response.)
-* A `403` response would indicate that verification failed **determinately** and no error object should be returned.
-* Any other response would indicate that verification failed **indeterminately** and an error object should be returned.
-
-### Adding Protos in Windows
-
-1. Install Ubuntu App in Microsoft Store https://www.microsoft.com/en-us/p/ubuntu/9nblggh4msv6.
-2. Install Docker Desktop https://www.docker.com/products/docker-desktop. Enable WSL integration to Ubuntu. In Docker app, go to Settings->Resources->WSL INTEGRATION->enable Ubuntu.
-3. Open Ubuntu cli and install `dos2unix`.
- ```bash
- sudo apt install dos2unix
- ```
-4. Identify the `trufflehog` local directory and convert `scripts/gen_proto.sh` file in Unix format.
- ```bash
- dos2unix ./scripts/gen_proto.sh
- ```
-5. Open [/proto/detectors.proto](/proto/detectors.proto) file and add new detectors then save it. Make sure Docker is running and run this in Ubuntu command line.
- ```bash
- make protos
- ```
-
diff --git a/hack/generate/generate.go b/hack/generate/generate.go
deleted file mode 100644
index 68ff6211384f..000000000000
--- a/hack/generate/generate.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "os"
- "path/filepath"
- "strings"
- "text/template"
-
- "github.com/alecthomas/kingpin/v2"
- "github.com/go-errors/errors"
- "golang.org/x/text/cases"
- "golang.org/x/text/language"
-)
-
-var (
- app = kingpin.New("generate", "Generate is used to write new features.")
- kind = app.Arg("kind", "Kind of thing to generate.").Required().Enum("detector")
- name = app.Arg("name", "Name of the Source/Detector to generate.").Required().String()
- nameTitle, nameLower, nameUpper string
-)
-
-func main() {
- log.SetFlags(log.Lmsgprefix)
- log.SetPrefix("😲 [generate] ")
-
- kingpin.MustParse(app.Parse(os.Args[1:]))
- nameTitle = cases.Title(language.AmericanEnglish).String(*name)
- nameLower = strings.ToLower(*name)
- nameUpper = strings.ToUpper(*name)
-
- switch *kind {
- case "detector":
- mustWriteTemplates([]templateJob{
- {
- TemplatePath: "pkg/detectors/alchemy/alchemy.go",
- WritePath: filepath.Join(folderPath(), nameLower+".go"),
- ReplaceString: []string{"alchemy"},
- },
- {
- TemplatePath: "pkg/detectors/alchemy/alchemy_test.go",
- WritePath: filepath.Join(folderPath(), nameLower+"_test.go"),
- ReplaceString: []string{"alchemy"},
- },
- {
- TemplatePath: "pkg/detectors/alchemy/alchemy_integration_test.go",
- WritePath: filepath.Join(folderPath(), nameLower+"_integration_test.go"),
- ReplaceString: []string{"alchemy"},
- },
- })
- // case "source":
- // mustWriteTemplates([]templateJob{
- // {
- // TemplatePath: "pkg/sources/filesystem/filesystem.go",
- // WritePath: filepath.Join(folderPath(), nameLower+".go"),
- // ReplaceString: []string{"filesystem"},
- // },
- // {
- // TemplatePath: "pkg/sources/filesystem/filesystem_test.go",
- // WritePath: filepath.Join(folderPath(), nameLower+"_test.go"),
- // ReplaceString: []string{"filesystem"},
- // },
- // })
- }
-}
-
-type templateJob struct {
- TemplatePath string
- WritePath string
- ReplaceString []string
-}
-
-func mustWriteTemplates(jobs []templateJob) {
- log.Printf("Generating %s %s\n", cases.Title(language.AmericanEnglish).String(*kind), nameTitle)
-
- // Make the folder.
- log.Printf("Creating folder %s\n", folderPath())
- err := makeFolder(folderPath())
- if err != nil {
- log.Fatal(err)
- }
-
- // Write the files from templates.
- for _, job := range jobs {
- tmplBytes, err := os.ReadFile(job.TemplatePath)
- if err != nil {
- log.Fatal(err)
- }
- tmplRaw := string(tmplBytes)
-
- for _, rplString := range job.ReplaceString {
- rplTitle := cases.Title(language.AmericanEnglish).String(rplString)
- tmplRaw = strings.ReplaceAll(tmplRaw, "DetectorType_"+rplTitle, "DetectorType_<<.Name>>")
- tmplRaw = strings.ReplaceAll(tmplRaw, strings.ToLower(rplString), "<<.NameLower>>")
- tmplRaw = strings.ReplaceAll(tmplRaw, rplTitle, "<<.NameTitle>>")
- tmplRaw = strings.ReplaceAll(tmplRaw, strings.ToUpper(rplString), "<<.NameUpper>>")
- }
-
- tmpl := template.Must(template.New("main").Delims("<<", ">>").Parse(tmplRaw))
-
- log.Printf("Writing file %s\n", job.WritePath)
- f, err := os.OpenFile(job.WritePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
- if err != nil {
- log.Fatal(err)
- }
- err = tmpl.Execute(f, templateData{
- Name: *name,
- NameTitle: nameTitle,
- NameLower: nameLower,
- NameUpper: nameUpper,
- })
- if err != nil {
- log.Fatal(fmt.Errorf("failed to execute template: %w", err))
- }
- }
-}
-
-type templateData struct {
- Name string
- NameTitle string
- NameLower string
- NameUpper string
-}
-
-func folderPath() string {
- return filepath.Join("pkg/", *kind+"s", nameLower)
-}
-
-func makeFolder(path string) error {
- _, err := os.Stat(path)
- if os.IsNotExist(err) {
- err := os.MkdirAll(path, 0755)
- if err != nil {
- return errors.New(err)
- }
- return nil
- }
- return errors.Errorf("%s %s already exists", *kind, *name)
-}
diff --git a/hack/generate/test.sh b/hack/generate/test.sh
deleted file mode 100755
index 6cb01a48cfa9..000000000000
--- a/hack/generate/test.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env bash
-set -eu
-
-function cleanup {
- rm -rf pkg/detectors/test
-}
-trap cleanup EXIT
-
-export CGO_ENABLED=0
-
-export FORCE_PASS_DIFF=true
-
-echo "████████████ Testing generate Detector"
-go run hack/generate/generate.go detector Test
-go test ./pkg/detectors/test -benchmem -bench .
-echo ""
diff --git a/hack/semgrep-rules/detectors.yaml b/hack/semgrep-rules/detectors.yaml
deleted file mode 100644
index ab1ed5c788c2..000000000000
--- a/hack/semgrep-rules/detectors.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-rules:
- - id: no-printing-in-detectors
- patterns:
- - pattern-either:
- - pattern: fmt.Println(...)
- - pattern: fmt.Printf(...)
- - pattern: import("log")
- message: "Do not print or log inside of detectors."
- languages: [go]
- severity: ERROR
diff --git a/hack/snifftest/README.md b/hack/snifftest/README.md
deleted file mode 100644
index dcf76f606d61..000000000000
--- a/hack/snifftest/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# snifftest
-
-See the help pages with this command, or look further below to get started quickly.
-
-```
-go run hack/snifftest/main.go
-```
-
-## Show available secret scanners
-
-```
-go run hack/snifftest/main.go show-scanners
-```
-
-## Scan
-
-All scanners
-
-```
-go run snifftest/main.go scan --db ~/sdb --scanner all --print
-```
-
-Particular scanner
-
-```
-go run snifftest/main.go scan --db ~/sdb --scanner github --print --print-chunk --fail-threshold 5
-```
diff --git a/hack/snifftest/main.go b/hack/snifftest/main.go
deleted file mode 100644
index 452021197bd5..000000000000
--- a/hack/snifftest/main.go
+++ /dev/null
@@ -1,256 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "reflect"
- "runtime"
- "strings"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/alecthomas/kingpin/v2"
- "github.com/paulbellamy/ratecounter"
- "golang.org/x/sync/semaphore"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults"
- "github.com/trufflesecurity/trufflehog/v3/pkg/log"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
-)
-
-var (
- // CLI flags and commands
- app = kingpin.New("Snifftest", "Test secret detectors against data sets.")
-
- showDetectorsCmd = app.Command("show-detectors", "Shows the available detectors.")
-
- scanCmd = app.Command("scan", "Scans data.")
- scanCmdDetector = scanCmd.Flag("detector", "Detector to scan with. 'all', or a specific name.").Default("all").String()
- scanCmdExclude = scanCmd.Flag("exclude", "Detector(s) to exclude").Strings()
- scanCmdRepo = scanCmd.Flag("repo", "URI to .git repo.").Required().String()
- scanThreshold = scanCmd.Flag("fail-threshold", "Result threshold that causes failure for a single scanner.").Int()
- scanPrintRes = scanCmd.Flag("print", "Print results.").Bool()
- scanPrintChunkRes = scanCmd.Flag("print-chunk", "Print chunks that have results.").Bool()
- scanVerify = scanCmd.Flag("verify", "Verify found secrets.").Bool()
-)
-
-func main() {
- // setup logger
- logger, flush := log.New("trufflehog", log.WithConsoleSink(os.Stderr))
- // make it the default logger for contexts
- context.SetDefaultLogger(logger)
- defer func() { _ = flush() }()
- logFatal := func(err error, message string, keyAndVals ...any) {
- logger.Error(err, message, keyAndVals...)
- if err != nil {
- os.Exit(1)
- return
- }
- os.Exit(0)
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), time.Hour*2)
- var cancelOnce sync.Once
- defer cancelOnce.Do(cancel)
-
- cmd := kingpin.MustParse(app.Parse(os.Args[1:]))
-
- switch cmd {
- case scanCmd.FullCommand():
-
- chunksChan := make(chan *sources.Chunk, 10000)
-
- var wgChunkers sync.WaitGroup
-
- sem := semaphore.NewWeighted(int64(runtime.NumCPU()))
-
- selectedScanners := map[string]detectors.Detector{}
- allScanners := getAllScanners()
-
- allDecoders := decoders.DefaultDecoders()
-
- input := strings.ToLower(*scanCmdDetector)
- if input == "all" {
- selectedScanners = allScanners
- } else {
- _, ok := allScanners[input]
- if !ok {
- logFatal(fmt.Errorf("invalid input"), "could not find scanner by that name")
- }
- selectedScanners[input] = allScanners[input]
- }
- if len(selectedScanners) == 0 {
- logFatal(fmt.Errorf("invalid input"), "no detectors selected")
- }
-
- for _, excluded := range *scanCmdExclude {
- delete(selectedScanners, excluded)
- }
-
- logger.Info("loaded secret detectors", "count", len(selectedScanners)+3)
-
- var wgScanners sync.WaitGroup
-
- var chunkCounter uint64
- go func() {
- counter := ratecounter.NewRateCounter(60 * time.Second)
- var prev uint64
- for {
- time.Sleep(60 * time.Second)
- counter.Incr(int64(chunkCounter - prev))
- prev = chunkCounter
- logger.Info("chunk scan rate per second", "rate", counter.Rate()/60)
- }
- }()
-
- resCounter := make(map[string]*uint64)
- failed := false
-
- for i := 0; i < runtime.NumCPU(); i++ {
- wgScanners.Add(1)
-
- go func() {
- defer wgScanners.Done()
-
- for chunk := range chunksChan {
- for name, scanner := range selectedScanners {
- for _, dec := range allDecoders {
- decoded := dec.FromChunk(&sources.Chunk{Data: chunk.Data})
- if decoded != nil {
- foundKeyword := false
- for _, kw := range scanner.Keywords() {
- if strings.Contains(strings.ToLower(string(decoded.Data)), strings.ToLower(kw)) {
- foundKeyword = true
- }
- }
- if !foundKeyword {
- continue
- }
-
- res, err := scanner.FromData(ctx, *scanVerify, decoded.Data)
- if err != nil {
- logFatal(err, "error scanning chunk")
- }
- if len(res) > 0 {
- if resCounter[name] == nil {
- zero := uint64(0)
- resCounter[name] = &zero
- }
- atomic.AddUint64(resCounter[name], uint64(len(res)))
- if *scanThreshold != 0 && int(*resCounter[name]) > *scanThreshold {
- logger.Error(
- fmt.Errorf("exceeded result threshold"), "snifftest failed",
- "scanner", name, "threshold", *scanThreshold,
- )
- failed = true
- os.Exit(1)
- }
-
- if *scanPrintRes {
- for _, r := range res {
- logger := logger.WithValues("secret", name, "meta", chunk.SourceMetadata, "result", string(r.Raw))
- if *scanPrintChunkRes {
- logger = logger.WithValues("chunk", string(decoded.Data))
- }
- logger.Info("result")
- }
- }
- }
- }
- }
- }
-
- atomic.AddUint64(&chunkCounter, uint64(1))
- }
- }()
- }
-
- for _, repo := range strings.Split(*scanCmdRepo, ",") {
- if err := sem.Acquire(ctx, 1); err != nil {
- logFatal(err, "timed out waiting for semaphore")
- }
- wgChunkers.Add(1)
- go func(r string) {
- defer sem.Release(1)
- defer wgChunkers.Done()
- logger.Info("cloning repo", "repo", r)
- path, repo, err := git.CloneRepoUsingUnauthenticated(ctx, r, "")
- if err != nil {
- logFatal(err, "error cloning repo", "repo", r)
- }
-
- logger.Info("cloned repo", "repo", r)
-
- cfg := &git.Config{
- SourceName: "snifftest",
- JobID: 0,
- SourceID: 0,
- SourceType: sourcespb.SourceType_SOURCE_TYPE_GIT,
- Verify: false,
- SkipBinaries: true,
- SkipArchives: false,
- Concurrency: runtime.NumCPU(),
- SourceMetadataFunc: func(file, email, commit, timestamp, repository, repositoryLocalPath string, line int64) *source_metadatapb.MetaData {
- return &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Git{
- Git: &source_metadatapb.Git{
- Commit: commit,
- File: file,
- Email: email,
- Repository: repository,
- Timestamp: timestamp,
- },
- },
- }
- },
- }
- s := git.NewGit(cfg)
-
- logger.Info("scanning repo", "repo", r)
- err = s.ScanRepo(ctx, repo, path, git.NewScanOptions(), sources.ChanReporter{Ch: chunksChan})
- if err != nil {
- logFatal(err, "error scanning repo")
- }
- logger.Info("scanned repo", "repo", r)
- defer os.RemoveAll(path)
- }(repo)
- }
-
- go func() {
- wgChunkers.Wait()
- close(chunksChan)
- }()
-
- wgScanners.Wait()
-
- logger.Info("completed snifftest", "chunks", chunkCounter)
- for scanner, resultsCount := range resCounter {
- logger.Info(scanner, "results", *resultsCount)
- }
-
- if failed {
- os.Exit(1)
- }
- case showDetectorsCmd.FullCommand():
- for s := range getAllScanners() {
- fmt.Println(s)
- }
- }
-}
-
-func getAllScanners() map[string]detectors.Detector {
- allScanners := map[string]detectors.Detector{}
- for _, s := range defaults.DefaultDetectors() {
- secretType := reflect.Indirect(reflect.ValueOf(s)).Type().PkgPath()
- path := strings.Split(secretType, "/")[len(strings.Split(secretType, "/"))-1]
- allScanners[path] = s
- }
- return allScanners
-}
diff --git a/hack/snifftest/snifftest.sh b/hack/snifftest/snifftest.sh
deleted file mode 100755
index f17b04dfbc1e..000000000000
--- a/hack/snifftest/snifftest.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env bash
-
-REPO_ARRAY=(
- "https://github.com/Netflix/Hystrix.git"
- # "https://github.com/facebook/flow.git"
- # "https://github.com/Netflix/vizceral.git"
- # "https://github.com/Netflix/metaflow.git"
- # "https://github.com/Netflix/dgs-framework.git"
- # "https://github.com/Netflix/vector.git"
- # "https://github.com/expressjs/express.git"
- # "https://github.com/Azure/azure-sdk-for-net"
- # "https://github.com/Azure/azure-cli"
-)
-REPOS=$(printf "%s," "${REPO_ARRAY[@]}" | cut -d "," -f 1-${#REPO_ARRAY[@]})
-go run hack/snifftest/main.go scan --exclude privatekey --exclude uri --exclude github_old --repo "$REPOS" --detector all --print --fail-threshold 99
\ No newline at end of file
diff --git a/main.go b/main.go
deleted file mode 100644
index 00ca4f7dfb56..000000000000
--- a/main.go
+++ /dev/null
@@ -1,1211 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- _ "net/http/pprof"
- "os"
- "os/exec"
- "os/signal"
- "runtime"
- "strconv"
- "strings"
- "sync"
- "syscall"
-
- "github.com/alecthomas/kingpin/v2"
- "github.com/fatih/color"
- "github.com/felixge/fgprof"
- "github.com/go-logr/logr"
- "github.com/jpillora/overseer"
- "github.com/mattn/go-isatty"
- "go.uber.org/automaxprocs/maxprocs"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- "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"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults"
- "github.com/trufflesecurity/trufflehog/v3/pkg/feature"
- "github.com/trufflesecurity/trufflehog/v3/pkg/handlers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/log"
- "github.com/trufflesecurity/trufflehog/v3/pkg/output"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/tui"
- "github.com/trufflesecurity/trufflehog/v3/pkg/updater"
- "github.com/trufflesecurity/trufflehog/v3/pkg/verificationcache"
- "github.com/trufflesecurity/trufflehog/v3/pkg/version"
-)
-
-var (
- cli = kingpin.New("TruffleHog", "TruffleHog is a tool for finding credentials.")
- cmd string
- // https://github.com/trufflesecurity/trufflehog/blob/main/CONTRIBUTING.md#logging-in-trufflehog
- logLevel = cli.Flag("log-level", `Logging verbosity on a scale of 0 (info) to 5 (trace). Can be disabled with "-1".`).Default("0").Int()
- debug = cli.Flag("debug", "Run in debug mode.").Hidden().Bool()
- trace = cli.Flag("trace", "Run in trace mode.").Hidden().Bool()
- profile = cli.Flag("profile", "Enables profiling and sets a pprof and fgprof server on :18066.").Bool()
- localDev = cli.Flag("local-dev", "Hidden feature to disable overseer for local dev.").Hidden().Bool()
- jsonOut = cli.Flag("json", "Output in JSON format.").Short('j').Bool()
- jsonLegacy = cli.Flag("json-legacy", "Use the pre-v3.0 JSON format. Only works with git, gitlab, and github sources.").Bool()
- gitHubActionsFormat = cli.Flag("github-actions", "Output in GitHub Actions format.").Bool()
- concurrency = cli.Flag("concurrency", "Number of concurrent workers.").Default(strconv.Itoa(runtime.NumCPU())).Int()
- noVerification = cli.Flag("no-verification", "Don't verify the results.").Bool()
- onlyVerified = cli.Flag("only-verified", "Only output verified results.").Hidden().Bool()
- results = cli.Flag("results", "Specifies which type(s) of results to output: verified (confirmed valid by API), unknown (verification failed due to error), unverified (detected but not verified), filtered_unverified (unverified but would have been filtered out). Defaults to verified,unverified,unknown.").String()
- noColor = cli.Flag("no-color", "Disable colorized output").Bool()
- noColour = cli.Flag("no-colour", "Alias for --no-color").Hidden().Bool()
-
- allowVerificationOverlap = cli.Flag("allow-verification-overlap", "Allow verification of similar credentials across detectors").Bool()
- filterUnverified = cli.Flag("filter-unverified", "Only output first unverified result per chunk per detector if there are more than one results.").Bool()
- filterEntropy = cli.Flag("filter-entropy", "Filter unverified results with Shannon entropy. Start with 3.0.").Float64()
- scanEntireChunk = cli.Flag("scan-entire-chunk", "Scan the entire chunk for secrets.").Hidden().Default("false").Bool()
- compareDetectionStrategies = cli.Flag("compare-detection-strategies", "Compare different detection strategies for matching spans").Hidden().Default("false").Bool()
- configFilename = cli.Flag("config", "Path to configuration file.").ExistingFile()
- // rules = cli.Flag("rules", "Path to file with custom rules.").String()
- printAvgDetectorTime = cli.Flag("print-avg-detector-time", "Print the average time spent on each detector.").Bool()
- noUpdate = cli.Flag("no-update", "Don't check for updates.").Bool()
- fail = cli.Flag("fail", "Exit with code 183 if results are found.").Bool()
- failOnScanErrors = cli.Flag("fail-on-scan-errors", "Exit with non-zero error code if an error occurs during the scan.").Bool()
- verifiers = cli.Flag("verifier", "Set custom verification endpoints.").StringMap()
- customVerifiersOnly = cli.Flag("custom-verifiers-only", "Only use custom verification endpoints.").Bool()
- detectorTimeout = cli.Flag("detector-timeout", "Maximum time to spend scanning chunks per detector (e.g., 30s).").Duration()
- archiveMaxSize = cli.Flag("archive-max-size", "Maximum size of archive to scan. (Byte units eg. 512B, 2KB, 4MB)").Bytes()
- archiveMaxDepth = cli.Flag("archive-max-depth", "Maximum depth of archive to scan.").Int()
- archiveTimeout = cli.Flag("archive-timeout", "Maximum time to spend extracting an archive.").Duration()
- includeDetectors = cli.Flag("include-detectors", "Comma separated list of detector types to include. Protobuf name or IDs may be used, as well as ranges.").Default("all").String()
- excludeDetectors = cli.Flag("exclude-detectors", "Comma separated list of detector types to exclude. Protobuf name or IDs may be used, as well as ranges. IDs defined here take precedence over the include list.").String()
- jobReportFile = cli.Flag("output-report", "Write a scan report to the provided path.").Hidden().OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
-
- noVerificationCache = cli.Flag("no-verification-cache", "Disable verification caching").Bool()
-
- // Add feature flags
- forceSkipBinaries = cli.Flag("force-skip-binaries", "Force skipping binaries.").Bool()
- forceSkipArchives = cli.Flag("force-skip-archives", "Force skipping archives.").Bool()
- gitCloneTimeout = cli.Flag("git-clone-timeout", "Maximum time to spend cloning a repository, as a duration.").Hidden().Duration()
- skipAdditionalRefs = cli.Flag("skip-additional-refs", "Skip additional references.").Bool()
- userAgentSuffix = cli.Flag("user-agent-suffix", "Suffix to add to User-Agent.").String()
-
- gitScan = cli.Command("git", "Find credentials in git repositories.")
- gitScanURI = gitScan.Arg("uri", "Git repository URL. https://, file://, or ssh:// schema expected.").Required().String()
- gitScanIncludePaths = gitScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
- gitScanExcludePaths = gitScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
- gitScanExcludeGlobs = gitScan.Flag("exclude-globs", "Comma separated list of globs to exclude in scan. This option filters at the `git log` level, resulting in faster scans.").String()
- gitScanSinceCommit = gitScan.Flag("since-commit", "Commit to start scan from.").String()
- gitScanBranch = gitScan.Flag("branch", "Branch to scan.").String()
- gitScanMaxDepth = gitScan.Flag("max-depth", "Maximum depth of commits to scan.").Int()
- gitScanBare = gitScan.Flag("bare", "Scan bare repository (e.g. useful while using in pre-receive hooks)").Bool()
- gitClonePath = gitScan.Flag("clone-path", "Custom path where the repository should be cloned (default: temp dir).").String()
- gitNoCleanup = gitScan.Flag("no-cleanup", "Do not delete cloned repositories after scanning (can only be used with --clone-path).").Bool()
- gitTrustLocalGitConfig = gitScan.Flag("trust-local-git-config", "Trust local git config.").Bool()
- _ = gitScan.Flag("allow", "No-op flag for backwards compat.").Bool()
- _ = gitScan.Flag("entropy", "No-op flag for backwards compat.").Bool()
- _ = gitScan.Flag("regex", "No-op flag for backwards compat.").Bool()
-
- githubScan = cli.Command("github", "Find credentials in GitHub repositories.")
- githubScanEndpoint = githubScan.Flag("endpoint", "GitHub endpoint.").Default("https://api.github.com").String()
- githubScanRepos = githubScan.Flag("repo", `GitHub repository to scan. You can repeat this flag. Example: "https://github.com/dustin-decker/secretsandstuff"`).Strings()
- githubScanOrgs = githubScan.Flag("org", `GitHub organization to scan. You can repeat this flag. Example: "trufflesecurity"`).Strings()
- githubScanToken = githubScan.Flag("token", "GitHub token. Can be provided with environment variable GITHUB_TOKEN.").Envar("GITHUB_TOKEN").String()
- githubIncludeForks = githubScan.Flag("include-forks", "Include forks in scan.").Bool()
- githubIncludeMembers = githubScan.Flag("include-members", "Include organization member repositories in scan.").Bool()
- githubIncludeRepos = githubScan.Flag("include-repos", `Repositories to include in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Github repo full name. Example: "trufflesecurity/trufflehog", "trufflesecurity/t*"`).Strings()
- githubIncludeWikis = githubScan.Flag("include-wikis", "Include repository wikisin scan.").Bool()
- githubExcludeRepos = githubScan.Flag("exclude-repos", `Repositories to exclude in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Github repo full name. Example: "trufflesecurity/driftwood", "trufflesecurity/d*"`).Strings()
- githubScanIncludePaths = githubScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
- githubScanExcludePaths = githubScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
- githubScanIssueComments = githubScan.Flag("issue-comments", "Include issue descriptions and comments in scan.").Bool()
- githubScanPRComments = githubScan.Flag("pr-comments", "Include pull request descriptions and comments in scan.").Bool()
- githubScanGistComments = githubScan.Flag("gist-comments", "Include gist comments in scan.").Bool()
- githubCommentsTimeframeDays = githubScan.Flag("comments-timeframe", "Number of days in the past to review when scanning issue, PR, and gist comments.").Uint32()
- githubAuthInUrl = githubScan.Flag("auth-in-url", "Embed authentication credentials in repository URLs instead of using secure HTTP headers").Bool()
- githubClonePath = githubScan.Flag("clone-path", "Custom path where the repository should be cloned (default: temp dir).").String()
- githubNoCleanup = githubScan.Flag("no-cleanup", "Do not delete cloned repositories after scanning (can only be used with --clone-path).").Bool()
- githubIgnoreGists = githubScan.Flag("ignore-gists", "Ignore all gists in scan.").Bool()
-
- // GitHub Cross Fork Object Reference Experimental Feature
- githubExperimentalScan = cli.Command("github-experimental", "Run an experimental GitHub scan. Must specify at least one experimental sub-module to run: object-discovery.")
- // GitHub Experimental SubModules
- githubExperimentalObjectDiscovery = githubExperimentalScan.Flag("object-discovery", "Discover hidden data objects in GitHub repositories.").Bool()
- // GitHub Experimental Options
- githubExperimentalToken = githubExperimentalScan.Flag("token", "GitHub token. Can be provided with environment variable GITHUB_TOKEN.").Envar("GITHUB_TOKEN").String()
- githubExperimentalRepo = githubExperimentalScan.Flag("repo", "GitHub repository to scan. Example: https://github.com//.git").Required().String()
- githubExperimentalCollisionThreshold = githubExperimentalScan.Flag("collision-threshold", "Threshold for short-sha collisions in object-discovery submodule. Default is 1.").Default("1").Int()
- githubExperimentalDeleteCache = githubExperimentalScan.Flag("delete-cached-data", "Delete cached data after object-discovery secret scanning.").Bool()
-
- gitlabScan = cli.Command("gitlab", "Find credentials in GitLab repositories.")
- // TODO: Add more GitLab options
- gitlabScanEndpoint = gitlabScan.Flag("endpoint", "GitLab endpoint.").Default("https://gitlab.com").String()
- gitlabScanRepos = gitlabScan.Flag("repo", "GitLab repo url. You can repeat this flag. Leave empty to scan all repos accessible with provided credential. Example: https://gitlab.com/org/repo.git").Strings()
- gitlabScanToken = gitlabScan.Flag("token", "GitLab token. Can be provided with environment variable GITLAB_TOKEN.").Envar("GITLAB_TOKEN").Required().String()
- gitlabScanGroupIds = gitlabScan.Flag("group-id", "GitLab group ID. If provided, it will scan the group and its subgroups. You can repeat this flag.").Strings()
- gitlabScanIncludePaths = gitlabScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
- gitlabScanExcludePaths = gitlabScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
- gitlabScanIncludeRepos = gitlabScan.Flag("include-repos", `Repositories to include in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Gitlab repo full name. Example: "trufflesecurity/trufflehog", "trufflesecurity/t*"`).Strings()
- gitlabScanExcludeRepos = gitlabScan.Flag("exclude-repos", `Repositories to exclude in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Gitlab repo full name. Example: "trufflesecurity/driftwood", "trufflesecurity/d*"`).Strings()
- gitlabAuthInUrl = gitlabScan.Flag("auth-in-url", "Embed authentication credentials in repository URLs instead of using secure HTTP headers").Bool()
- gitlabClonePath = gitlabScan.Flag("clone-path", "Custom path where the repository should be cloned (default: temp dir)").String()
- gitlabNoCleanup = gitlabScan.Flag("no-cleanup", "Do not delete cloned repositories after scanning (can only be used with --clone-path).").Bool()
-
- filesystemScan = cli.Command("filesystem", "Find credentials in a filesystem.")
- filesystemPaths = filesystemScan.Arg("path", "Path to file or directory to scan.").Strings()
- // DEPRECATED: --directory is deprecated in favor of arguments.
- filesystemDirectories = filesystemScan.Flag("directory", "Path to directory to scan. You can repeat this flag.").Strings()
- // TODO: Add more filesystem scan options. Currently only supports scanning a list of directories.
- // filesystemScanRecursive = filesystemScan.Flag("recursive", "Scan recursively.").Short('r').Bool()
- filesystemScanIncludePaths = filesystemScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
- filesystemScanExcludePaths = filesystemScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
-
- s3Scan = cli.Command("s3", "Find credentials in S3 buckets.")
- s3ScanKey = s3Scan.Flag("key", "S3 key used to authenticate. Can be provided with environment variable AWS_ACCESS_KEY_ID.").Envar("AWS_ACCESS_KEY_ID").String()
- s3ScanRoleArns = s3Scan.Flag("role-arn", "Specify the ARN of an IAM role to assume for scanning. You can repeat this flag.").Strings()
- s3ScanSecret = s3Scan.Flag("secret", "S3 secret used to authenticate. Can be provided with environment variable AWS_SECRET_ACCESS_KEY.").Envar("AWS_SECRET_ACCESS_KEY").String()
- s3ScanSessionToken = s3Scan.Flag("session-token", "S3 session token used to authenticate temporary credentials. Can be provided with environment variable AWS_SESSION_TOKEN.").Envar("AWS_SESSION_TOKEN").String()
- s3ScanCloudEnv = s3Scan.Flag("cloud-environment", "Use IAM credentials in cloud environment.").Bool()
- s3ScanBuckets = s3Scan.Flag("bucket", "Name of S3 bucket to scan. You can repeat this flag. Incompatible with --ignore-bucket.").Strings()
- s3ScanIgnoreBuckets = s3Scan.Flag("ignore-bucket", "Name of S3 bucket to ignore. You can repeat this flag. Incompatible with --bucket.").Strings()
- s3ScanMaxObjectSize = s3Scan.Flag("max-object-size", "Maximum size of objects to scan. Objects larger than this will be skipped. (Byte units eg. 512B, 2KB, 4MB)").Default("250MB").Bytes()
-
- gcsScan = cli.Command("gcs", "Find credentials in GCS buckets.")
- gcsProjectID = gcsScan.Flag("project-id", "GCS project ID used to authenticate. Can NOT be used with unauth scan. Can be provided with environment variable GOOGLE_CLOUD_PROJECT.").Envar("GOOGLE_CLOUD_PROJECT").String()
- gcsCloudEnv = gcsScan.Flag("cloud-environment", "Use Application Default Credentials, IAM credentials to authenticate.").Bool()
- gcsServiceAccount = gcsScan.Flag("service-account", "Path to GCS service account JSON file.").ExistingFile()
- gcsWithoutAuth = gcsScan.Flag("without-auth", "Scan GCS buckets without authentication. This will only work for public buckets").Bool()
- gcsAPIKey = gcsScan.Flag("api-key", "GCS API key used to authenticate. Can be provided with environment variable GOOGLE_API_KEY.").Envar("GOOGLE_API_KEY").String()
- gcsIncludeBuckets = gcsScan.Flag("include-buckets", "Buckets to scan. Comma separated list of buckets. You can repeat this flag. Globs are supported").Short('I').Strings()
- gcsExcludeBuckets = gcsScan.Flag("exclude-buckets", "Buckets to exclude from scan. Comma separated list of buckets. Globs are supported").Short('X').Strings()
- gcsIncludeObjects = gcsScan.Flag("include-objects", "Objects to scan. Comma separated list of objects. you can repeat this flag. Globs are supported").Short('i').Strings()
- gcsExcludeObjects = gcsScan.Flag("exclude-objects", "Objects to exclude from scan. Comma separated list of objects. You can repeat this flag. Globs are supported").Short('x').Strings()
- gcsMaxObjectSize = gcsScan.Flag("max-object-size", "Maximum size of objects to scan. Objects larger than this will be skipped. (Byte units eg. 512B, 2KB, 4MB)").Default("10MB").Bytes()
-
- syslogScan = cli.Command("syslog", "Scan syslog")
- syslogAddress = syslogScan.Flag("address", "Address and port to listen on for syslog. Example: 127.0.0.1:514").String()
- syslogProtocol = syslogScan.Flag("protocol", "Protocol to listen on. udp or tcp").String()
- syslogTLSCert = syslogScan.Flag("cert", "Path to TLS cert.").String()
- syslogTLSKey = syslogScan.Flag("key", "Path to TLS key.").String()
- syslogFormat = syslogScan.Flag("format", "Log format. Can be rfc3164 or rfc5424").Required().String()
-
- circleCiScan = cli.Command("circleci", "Scan CircleCI")
- circleCiScanToken = circleCiScan.Flag("token", "CircleCI token. Can also be provided with environment variable").Envar("CIRCLECI_TOKEN").Required().String()
-
- dockerScan = cli.Command("docker", "Scan Docker Image")
- dockerScanImages = dockerScan.Flag("image", "Docker image to scan. Use the file:// prefix to point to a local tarball, the docker:// prefix to point to the docker daemon, otherwise an image registry is assumed.").Strings()
- dockerScanToken = dockerScan.Flag("token", "Docker bearer token. Can also be provided with environment variable").Envar("DOCKER_TOKEN").String()
- dockerExcludePaths = dockerScan.Flag("exclude-paths", "Comma separated list of paths to exclude from scan").String()
- dockerScanNamespace = dockerScan.Flag("namespace", "Docker namespace (organization or user). For non-Docker Hub registries, include the registry address as well (e.g., ghcr.io/namespace or quay.io/namespace).").String()
- dockerScanRegistryToken = dockerScan.Flag("registry-token", "Optional Docker registry access token. Provide this if you want to include private images within the specified namespace.").String()
-
- travisCiScan = cli.Command("travisci", "Scan TravisCI")
- travisCiScanToken = travisCiScan.Flag("token", "TravisCI token. Can also be provided with environment variable").Envar("TRAVISCI_TOKEN").Required().String()
-
- // Postman is hidden for now until we get more feedback from the community.
- postmanScan = cli.Command("postman", "Scan Postman")
- postmanToken = postmanScan.Flag("token", "Postman token. Can also be provided with environment variable").Envar("POSTMAN_TOKEN").String()
-
- postmanWorkspaces = postmanScan.Flag("workspace", "Postman workspace to scan. You can repeat this flag. Deprecated flag.").Hidden().Strings()
- postmanWorkspaceIDs = postmanScan.Flag("workspace-id", "Postman workspace ID to scan. You can repeat this flag.").Strings()
-
- postmanCollections = postmanScan.Flag("collection", "Postman collection to scan. You can repeat this flag. Deprecated flag.").Hidden().Strings()
- postmanCollectionIDs = postmanScan.Flag("collection-id", "Postman collection ID to scan. You can repeat this flag.").Strings()
-
- postmanEnvironments = postmanScan.Flag("environment", "Postman environment to scan. You can repeat this flag.").Strings()
-
- postmanIncludeCollections = postmanScan.Flag("include-collections", "Collections to include in scan. You can repeat this flag. Deprecated flag.").Hidden().Strings()
- postmanIncludeCollectionIDs = postmanScan.Flag("include-collection-id", "Collection ID to include in scan. You can repeat this flag.").Strings()
-
- postmanIncludeEnvironments = postmanScan.Flag("include-environments", "Environments to include in scan. You can repeat this flag.").Strings()
-
- postmanExcludeCollections = postmanScan.Flag("exclude-collections", "Collections to exclude from scan. You can repeat this flag. Deprecated flag.").Hidden().Strings()
- postmanExcludeCollectionIDs = postmanScan.Flag("exclude-collection-id", "Collection ID to exclude from scan. You can repeat this flag.").Strings()
-
- postmanExcludeEnvironments = postmanScan.Flag("exclude-environments", "Environments to exclude from scan. You can repeat this flag.").Strings()
- postmanWorkspacePaths = postmanScan.Flag("workspace-paths", "Path to Postman workspaces.").Strings()
- postmanCollectionPaths = postmanScan.Flag("collection-paths", "Path to Postman collections.").Strings()
- postmanEnvironmentPaths = postmanScan.Flag("environment-paths", "Path to Postman environments.").Strings()
-
- elasticsearchScan = cli.Command("elasticsearch", "Scan Elasticsearch")
- elasticsearchNodes = elasticsearchScan.Flag("nodes", "Elasticsearch nodes").Envar("ELASTICSEARCH_NODES").Strings()
- elasticsearchUsername = elasticsearchScan.Flag("username", "Elasticsearch username").Envar("ELASTICSEARCH_USERNAME").String()
- elasticsearchPassword = elasticsearchScan.Flag("password", "Elasticsearch password").Envar("ELASTICSEARCH_PASSWORD").String()
- elasticsearchServiceToken = elasticsearchScan.Flag("service-token", "Elasticsearch service token").Envar("ELASTICSEARCH_SERVICE_TOKEN").String()
- elasticsearchCloudId = elasticsearchScan.Flag("cloud-id", "Elasticsearch cloud ID. Can also be provided with environment variable").Envar("ELASTICSEARCH_CLOUD_ID").String()
- elasticsearchAPIKey = elasticsearchScan.Flag("api-key", "Elasticsearch API key. Can also be provided with environment variable").Envar("ELASTICSEARCH_API_KEY").String()
- elasticsearchIndexPattern = elasticsearchScan.Flag("index-pattern", "Filters the indices to search").Default("*").Envar("ELASTICSEARCH_INDEX_PATTERN").String()
- elasticsearchQueryJSON = elasticsearchScan.Flag("query-json", "Filters the documents to search").Envar("ELASTICSEARCH_QUERY_JSON").String()
- elasticsearchSinceTimestamp = elasticsearchScan.Flag("since-timestamp", "Filters the documents to search to those created since this timestamp; overrides any timestamp from --query-json").Envar("ELASTICSEARCH_SINCE_TIMESTAMP").String()
- elasticsearchBestEffortScan = elasticsearchScan.Flag("best-effort-scan", "Attempts to continuously scan a cluster").Envar("ELASTICSEARCH_BEST_EFFORT_SCAN").Bool()
-
- jenkinsScan = cli.Command("jenkins", "Scan Jenkins")
- jenkinsURL = jenkinsScan.Flag("url", "Jenkins URL").Envar("JENKINS_URL").Required().String()
- jenkinsUsername = jenkinsScan.Flag("username", "Jenkins username").Envar("JENKINS_USERNAME").String()
- jenkinsPassword = jenkinsScan.Flag("password", "Jenkins password").Envar("JENKINS_PASSWORD").String()
- jenkinsInsecureSkipVerifyTLS = jenkinsScan.Flag("insecure-skip-verify-tls", "Skip TLS verification").Envar("JENKINS_INSECURE_SKIP_VERIFY_TLS").Bool()
-
- huggingfaceScan = cli.Command("huggingface", "Find credentials in HuggingFace datasets, models and spaces.")
- huggingfaceEndpoint = huggingfaceScan.Flag("endpoint", "HuggingFace endpoint.").Default("https://huggingface.co").String()
- huggingfaceModels = huggingfaceScan.Flag("model", "HuggingFace model to scan. You can repeat this flag. Example: 'username/model'").Strings()
- huggingfaceSpaces = huggingfaceScan.Flag("space", "HuggingFace space to scan. You can repeat this flag. Example: 'username/space'").Strings()
- huggingfaceDatasets = huggingfaceScan.Flag("dataset", "HuggingFace dataset to scan. You can repeat this flag. Example: 'username/dataset'").Strings()
- huggingfaceOrgs = huggingfaceScan.Flag("org", `HuggingFace organization to scan. You can repeat this flag. Example: "trufflesecurity"`).Strings()
- huggingfaceUsers = huggingfaceScan.Flag("user", `HuggingFace user to scan. You can repeat this flag. Example: "trufflesecurity"`).Strings()
- huggingfaceToken = huggingfaceScan.Flag("token", "HuggingFace token. Can be provided with environment variable HUGGINGFACE_TOKEN.").Envar("HUGGINGFACE_TOKEN").String()
-
- huggingfaceIncludeModels = huggingfaceScan.Flag("include-models", "Models to include in scan. You can repeat this flag. Must use HuggingFace model full name. Example: 'username/model' (Only used with --user or --org)").Strings()
- huggingfaceIncludeSpaces = huggingfaceScan.Flag("include-spaces", "Spaces to include in scan. You can repeat this flag. Must use HuggingFace space full name. Example: 'username/space' (Only used with --user or --org)").Strings()
- huggingfaceIncludeDatasets = huggingfaceScan.Flag("include-datasets", "Datasets to include in scan. You can repeat this flag. Must use HuggingFace dataset full name. Example: 'username/dataset' (Only used with --user or --org)").Strings()
- huggingfaceIgnoreModels = huggingfaceScan.Flag("ignore-models", "Models to ignore in scan. You can repeat this flag. Must use HuggingFace model full name. Example: 'username/model' (Only used with --user or --org)").Strings()
- huggingfaceIgnoreSpaces = huggingfaceScan.Flag("ignore-spaces", "Spaces to ignore in scan. You can repeat this flag. Must use HuggingFace space full name. Example: 'username/space' (Only used with --user or --org)").Strings()
- huggingfaceIgnoreDatasets = huggingfaceScan.Flag("ignore-datasets", "Datasets to ignore in scan. You can repeat this flag. Must use HuggingFace dataset full name. Example: 'username/dataset' (Only used with --user or --org)").Strings()
- huggingfaceSkipAllModels = huggingfaceScan.Flag("skip-all-models", "Skip all model scans. (Only used with --user or --org)").Bool()
- huggingfaceSkipAllSpaces = huggingfaceScan.Flag("skip-all-spaces", "Skip all space scans. (Only used with --user or --org)").Bool()
- huggingfaceSkipAllDatasets = huggingfaceScan.Flag("skip-all-datasets", "Skip all dataset scans. (Only used with --user or --org)").Bool()
- huggingfaceIncludeDiscussions = huggingfaceScan.Flag("include-discussions", "Include discussions in scan.").Bool()
- huggingfaceIncludePrs = huggingfaceScan.Flag("include-prs", "Include pull requests in scan.").Bool()
-
- stdinInputScan = cli.Command("stdin", "Find credentials from stdin.")
- multiScanScan = cli.Command("multi-scan", "Find credentials in multiple sources defined in configuration.")
-
- analyzeCmd = analyzer.Command(cli)
- usingTUI = false
-)
-
-func init() {
- _, _ = maxprocs.Set()
-
- for i, arg := range os.Args {
- if strings.HasPrefix(arg, "--") {
- split := strings.SplitN(arg, "=", 2)
- split[0] = strings.ReplaceAll(split[0], "_", "-")
- os.Args[i] = strings.Join(split, "=")
- }
- }
-
- cli.Version("trufflehog " + version.BuildVersion)
-
- // Support -h for help and write it to stdout.
- cli.HelpFlag.Short('h')
- cli.UsageWriter(os.Stdout)
-
- // Check if the TUI environment variable is set.
- if ok, err := strconv.ParseBool(os.Getenv("TUI_PARENT")); err == nil {
- usingTUI = ok
- }
-
- if isatty.IsTerminal(os.Stdout.Fd()) && (len(os.Args) <= 1 || os.Args[1] == analyzeCmd.FullCommand()) {
- args := tui.Run(os.Args[1:])
- if len(args) == 0 {
- os.Exit(0)
- }
-
- binary, err := exec.LookPath("sh")
- if err == nil {
- // On success, this call will never return. On failure, fallthrough
- // to overwriting os.Args.
- cmd := strings.Join(append(os.Args[:1], args...), " ")
- _ = syscall.Exec(binary, []string{"sh", "-c", cmd}, append(os.Environ(), "TUI_PARENT=true"))
- }
-
- // Overwrite the Args slice so overseer works properly.
- os.Args = os.Args[:1]
- os.Args = append(os.Args, args...)
-
- usingTUI = true
- }
-
- cmd = kingpin.MustParse(cli.Parse(os.Args[1:]))
-
- // Configure logging.
- switch {
- case *trace:
- log.SetLevel(5)
- case *debug:
- log.SetLevel(2)
- default:
- l := int8(*logLevel)
- if l < -1 || l > 5 {
- fmt.Fprintf(os.Stderr, "invalid log level: %d\n", *logLevel)
- os.Exit(1)
- }
-
- if l == -1 {
- // Zap uses "5" as the value for fatal.
- // We need to pass in "-5" because `SetLevel` passes the negation.
- log.SetLevel(-5)
- } else {
- log.SetLevel(l)
- }
- }
-
- if *noColor || *noColour {
- color.NoColor = true // disables colorized output
- }
-}
-
-func main() {
- // setup logger
- logFormat := log.WithConsoleSink
- if *jsonOut {
- logFormat = log.WithJSONSink
- }
- logger, sync := log.New("trufflehog", logFormat(os.Stderr, log.WithGlobalRedaction()))
- // make it the default logger for contexts
- context.SetDefaultLogger(logger)
-
- if *localDev {
- run(overseer.State{})
- os.Exit(0)
- }
-
- defer func() { _ = sync() }()
- logFatal := logFatalFunc(logger)
-
- updateCfg := overseer.Config{
- Program: run,
- Debug: *debug,
- RestartSignal: syscall.SIGTERM,
- // TODO: Eventually add a PreUpgrade func for signature check w/ x509 PKCS1v15
- // PreUpgrade: checkUpdateSignature(binaryPath string),
- }
-
- if !*noUpdate {
- topLevelCmd, _, _ := strings.Cut(cmd, " ")
- updateCfg.Fetcher = updater.Fetcher(topLevelCmd, usingTUI)
- }
- if version.BuildVersion == "dev" {
- updateCfg.Fetcher = nil
- }
-
- err := overseer.RunErr(updateCfg)
- if err != nil {
- logFatal(err, "error occurred with trufflehog updater 🐷")
- }
-}
-
-func run(state overseer.State) {
-
- 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")
- }
- os.Exit(0)
- }()
-
- logger.V(2).Info(fmt.Sprintf("trufflehog %s", version.BuildVersion))
-
- if *githubScanToken != "" {
- // NOTE: this kludge is here to do an authenticated shallow commit
- // TODO: refactor to better pass credentials
- os.Setenv("GITHUB_TOKEN", *githubScanToken)
- }
-
- // When setting a base commit, chunks must be scanned in order.
- if *gitScanSinceCommit != "" {
- *concurrency = 1
- }
-
- if *profile {
- runtime.SetBlockProfileRate(1)
- runtime.SetMutexProfileFraction(-1)
- go func() {
- router := http.NewServeMux()
- router.Handle("/debug/pprof/", http.DefaultServeMux)
- router.Handle("/debug/fgprof", fgprof.Handler())
- logger.Info("starting pprof and fgprof server on :18066 /debug/pprof and /debug/fgprof")
- if err := http.ListenAndServe(":18066", router); err != nil {
- logger.Error(err, "error serving pprof and fgprof")
- }
- }()
- }
-
- // Set feature configurations from CLI flags
- if *forceSkipBinaries {
- feature.ForceSkipBinaries.Store(true)
- }
-
- if *forceSkipArchives {
- feature.ForceSkipArchives.Store(true)
- }
-
- if gitCloneTimeout != nil {
- feature.GitCloneTimeoutDuration.Store(int64(*gitCloneTimeout))
- }
-
- if *skipAdditionalRefs {
- feature.SkipAdditionalRefs.Store(true)
- }
-
- if *userAgentSuffix != "" {
- feature.UserAgentSuffix.Store(*userAgentSuffix)
- }
-
- // OSS Default APK handling on
- feature.EnableAPKHandler.Store(true)
-
- // OSS Default Use Git Mirror on
- feature.UseGitMirror.Store(true)
-
- // OSS Default simplified gitlab enumeration
- feature.UseSimplifiedGitlabEnumeration.Store(true)
- feature.GitlabProjectsPerPage.Store(100)
-
- // OSS Default using github graphql api for issues, pr's and comments
- feature.UseGithubGraphQLAPI.Store(false)
-
- conf := &config.Config{}
- if *configFilename != "" {
- var err error
- conf, err = config.Read(*configFilename)
- if err != nil {
- logFatal(err, "error parsing the provided configuration file")
- }
- }
-
- if *detectorTimeout != 0 {
- logger.Info("Setting detector timeout", "timeout", detectorTimeout.String())
- engine.SetDetectorTimeout(*detectorTimeout)
- detectors.OverrideDetectorTimeout(*detectorTimeout)
- }
- if *archiveMaxSize != 0 {
- handlers.SetArchiveMaxSize(int(*archiveMaxSize))
- }
- if *archiveMaxDepth != 0 {
- handlers.SetArchiveMaxDepth(*archiveMaxDepth)
- }
- if *archiveTimeout != 0 {
- handlers.SetArchiveMaxTimeout(*archiveTimeout)
- }
-
- // Set how the engine will print its results.
- var printer engine.Printer
- switch {
- case *jsonLegacy:
- printer = new(output.LegacyJSONPrinter)
- case *jsonOut:
- printer = new(output.JSONPrinter)
- case *gitHubActionsFormat:
- printer = new(output.GitHubActionsPrinter)
- default:
- printer = new(output.PlainPrinter)
- }
-
- if !*jsonLegacy && !*jsonOut {
- fmt.Fprintf(os.Stderr, "🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷\n\n")
- }
-
- // Parse --results flag.
- if *onlyVerified {
- r := "verified"
- results = &r
- }
- parsedResults, err := parseResults(results)
- if err != nil {
- logFatal(err, "failed to configure results flag")
- }
-
- verificationCacheMetrics := verificationcache.InMemoryMetrics{}
-
- engConf := engine.Config{
- Concurrency: *concurrency,
- ConfiguredSources: conf.Sources,
- // The engine must always be configured with the list of
- // default detectors, which can be further filtered by the
- // user. The filters are applied by the engine and are only
- // subtractive.
- Detectors: append(defaults.DefaultDetectors(), conf.Detectors...),
- Verify: !*noVerification,
- IncludeDetectors: *includeDetectors,
- ExcludeDetectors: *excludeDetectors,
- CustomVerifiersOnly: *customVerifiersOnly,
- VerifierEndpoints: *verifiers,
- Dispatcher: engine.NewPrinterDispatcher(printer),
- FilterUnverified: *filterUnverified,
- FilterEntropy: *filterEntropy,
- VerificationOverlap: *allowVerificationOverlap,
- Results: parsedResults,
- PrintAvgDetectorTime: *printAvgDetectorTime,
- ShouldScanEntireChunk: *scanEntireChunk,
- VerificationCacheMetrics: &verificationCacheMetrics,
- }
-
- if !*noVerificationCache {
- engConf.VerificationResultCache = simple.NewCache[detectors.Result]()
- }
-
- // Check that there are no sources defined for non-scan subcommands. If
- // there are, return an error as it is ambiguous what the user is
- // trying to do.
- if cmd != multiScanScan.FullCommand() && len(conf.Sources) > 0 {
- logFatal(
- fmt.Errorf("ambiguous configuration"),
- "sources should only be defined in configuration for the 'multi-scan' command",
- )
- }
-
- if *compareDetectionStrategies {
- if err := compareScans(ctx, cmd, engConf); err != nil {
- logFatal(err, "error comparing detection strategies")
- }
- return
- }
-
- metrics, err := runSingleScan(ctx, cmd, engConf)
- if err != nil {
- logFatal(err, "error running scan")
- }
-
- verificationCacheMetricsSnapshot := struct {
- Hits int32
- Misses int32
- HitsWasted int32
- AttemptsSaved int32
- VerificationTimeSpentMS int64
- }{
- Hits: verificationCacheMetrics.ResultCacheHits.Load(),
- Misses: verificationCacheMetrics.ResultCacheMisses.Load(),
- HitsWasted: verificationCacheMetrics.ResultCacheHitsWasted.Load(),
- AttemptsSaved: verificationCacheMetrics.CredentialVerificationsSaved.Load(),
- VerificationTimeSpentMS: verificationCacheMetrics.FromDataVerifyTimeSpentMS.Load(),
- }
-
- // Print results.
- logger.Info("finished scanning",
- "chunks", metrics.ChunksScanned,
- "bytes", metrics.BytesScanned,
- "verified_secrets", metrics.VerifiedSecretsFound,
- "unverified_secrets", metrics.UnverifiedSecretsFound,
- "scan_duration", metrics.ScanDuration.String(),
- "trufflehog_version", version.BuildVersion,
- "verification_caching", verificationCacheMetricsSnapshot,
- )
-
- if metrics.hasFoundResults && *fail {
- logger.V(2).Info("exiting with code 183 because results were found")
- os.Exit(183)
- }
-}
-
-func compareScans(ctx context.Context, cmd string, cfg engine.Config) error {
- var (
- entireMetrics metrics
- maxLengthMetrics metrics
- err error
- )
-
- var wg sync.WaitGroup
- wg.Add(1)
-
- go func() {
- defer wg.Done()
- // Run scan with entire chunk span calculator.
- cfg.ShouldScanEntireChunk = true
- entireMetrics, err = runSingleScan(ctx, cmd, cfg)
- if err != nil {
- ctx.Logger().Error(err, "error running scan with entire chunk span calculator")
- }
- }()
-
- // Run scan with max-length span calculator.
- maxLengthMetrics, err = runSingleScan(ctx, cmd, cfg)
- if err != nil {
- return fmt.Errorf("error running scan with custom span calculator: %v", err)
- }
-
- wg.Wait()
-
- return compareMetrics(maxLengthMetrics.Metrics, entireMetrics.Metrics)
-}
-
-func compareMetrics(customMetrics, entireMetrics engine.Metrics) error {
- fmt.Printf("Comparison of scan results: \n")
- fmt.Printf("Custom span - Chunks: %d, Bytes: %d, Verified Secrets: %d, Unverified Secrets: %d, Duration: %s\n",
- customMetrics.ChunksScanned, customMetrics.BytesScanned, customMetrics.VerifiedSecretsFound, customMetrics.UnverifiedSecretsFound, customMetrics.ScanDuration.String())
- fmt.Printf("Entire chunk - Chunks: %d, Bytes: %d, Verified Secrets: %d, Unverified Secrets: %d, Duration: %s\n",
- entireMetrics.ChunksScanned, entireMetrics.BytesScanned, entireMetrics.VerifiedSecretsFound, entireMetrics.UnverifiedSecretsFound, entireMetrics.ScanDuration.String())
-
- // Check for differences in scan metrics.
- if customMetrics.ChunksScanned != entireMetrics.ChunksScanned ||
- customMetrics.BytesScanned != entireMetrics.BytesScanned ||
- customMetrics.VerifiedSecretsFound != entireMetrics.VerifiedSecretsFound {
- return fmt.Errorf("scan metrics do not match")
- }
-
- return nil
-}
-
-type metrics struct {
- engine.Metrics
- hasFoundResults bool
-}
-
-func runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics, error) {
- var scanMetrics metrics
-
- // Setup job report writer if provided
- var jobReportWriter io.WriteCloser
- if *jobReportFile != nil {
- jobReportWriter = *jobReportFile
- }
-
- handleFinishedMetrics := func(ctx context.Context, finishedMetrics <-chan sources.UnitMetrics, jobReportWriter io.WriteCloser) {
- go func() {
- defer func() {
- jobReportWriter.Close()
- if namer, ok := jobReportWriter.(interface{ Name() string }); ok {
- ctx.Logger().Info("report written", "path", namer.Name())
- } else {
- ctx.Logger().Info("report written")
- }
- }()
-
- for metrics := range finishedMetrics {
- metrics.Errors = common.ExportErrors(metrics.Errors...)
- details, err := json.Marshal(map[string]any{
- "version": 1,
- "data": metrics,
- })
- if err != nil {
- ctx.Logger().Error(err, "error marshalling job details")
- continue
- }
- if _, err := jobReportWriter.Write(append(details, '\n')); err != nil {
- ctx.Logger().Error(err, "error writing to file")
- }
- }
- }()
- }
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithConcurrentSources(cfg.Concurrency),
- sources.WithConcurrentUnits(cfg.Concurrency),
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- if jobReportWriter != nil {
- unitHook, finishedMetrics := sources.NewUnitHook(ctx)
- opts = append(opts, sources.WithReportHook(unitHook))
- handleFinishedMetrics(ctx, finishedMetrics, jobReportWriter)
- }
-
- cfg.SourceManager = sources.NewManager(opts...)
-
- eng, err := engine.NewEngine(ctx, &cfg)
- if err != nil {
- return scanMetrics, fmt.Errorf("error initializing engine: %v", err)
- }
- eng.Start(ctx)
-
- persistGitRepo := *gitNoCleanup || *githubNoCleanup || *gitlabNoCleanup
- gitCloneTempPath := ""
-
- defer func() {
- // Clean up temporary artifacts.
- if err := cleantemp.CleanTempArtifacts(ctx); err != nil {
- ctx.Logger().Error(err, "error cleaning temp artifacts")
- }
-
- if *jsonLegacy {
- // If JSON legacy is enabled, that means the cloned repos are not deleted yet
- // because they were needed for outputting legacy JSON.
- // We only clean them up here if the user did not request to persist them.
- if !persistGitRepo {
- if err := cleantemp.CleanTempDirsForLegacyJSON(gitCloneTempPath); err != nil {
- ctx.Logger().Error(err, "error cleaning temp artifacts for legacy JSON")
- }
- }
- }
- }()
-
- var refs []sources.JobProgressRef
- switch cmd {
- case gitScan.FullCommand():
-
- if err := validateClonePath(*gitClonePath, *gitNoCleanup); err != nil {
- return scanMetrics, err
- }
-
- gitCfg := sources.GitConfig{
- URI: *gitScanURI,
- IncludePathsFile: *gitScanIncludePaths,
- ExcludePathsFile: *gitScanExcludePaths,
- HeadRef: *gitScanBranch,
- BaseRef: *gitScanSinceCommit,
- MaxDepth: *gitScanMaxDepth,
- Bare: *gitScanBare,
- ExcludeGlobs: *gitScanExcludeGlobs,
- ClonePath: *gitClonePath,
- NoCleanup: *gitNoCleanup,
- PrintLegacyJSON: *jsonLegacy,
- TrustLocalGitConfig: *gitTrustLocalGitConfig,
- }
- if ref, err := eng.ScanGit(ctx, gitCfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan Git: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case githubScan.FullCommand():
- gitCloneTempPath = *githubClonePath
- filter, err := common.FilterFromFiles(*githubScanIncludePaths, *githubScanExcludePaths)
- if err != nil {
- return scanMetrics, fmt.Errorf("could not create filter: %v", err)
- }
- if len(*githubScanOrgs) == 0 && len(*githubScanRepos) == 0 {
- return scanMetrics, fmt.Errorf("invalid config: you must specify at least one organization or repository")
- }
- if len(*githubScanOrgs) > 0 && len(*githubScanRepos) > 0 {
- return scanMetrics, fmt.Errorf("invalid config: you cannot specify both organizations and repositories at the same time")
- }
-
- if err := validateClonePath(*githubClonePath, *githubNoCleanup); err != nil {
- return scanMetrics, err
- }
-
- cfg := sources.GithubConfig{
- Endpoint: *githubScanEndpoint,
- Token: *githubScanToken,
- IncludeForks: *githubIncludeForks,
- IncludeMembers: *githubIncludeMembers,
- IncludeWikis: *githubIncludeWikis,
- Concurrency: *concurrency,
- ExcludeRepos: *githubExcludeRepos,
- IncludeRepos: *githubIncludeRepos,
- Repos: *githubScanRepos,
- Orgs: *githubScanOrgs,
- IncludeIssueComments: *githubScanIssueComments,
- IncludePullRequestComments: *githubScanPRComments,
- IncludeGistComments: *githubScanGistComments,
- CommentsTimeframeDays: *githubCommentsTimeframeDays,
- Filter: filter,
- AuthInUrl: *githubAuthInUrl,
- ClonePath: *githubClonePath,
- NoCleanup: *githubNoCleanup,
- IgnoreGists: *githubIgnoreGists,
- PrintLegacyJSON: *jsonLegacy,
- }
-
- if ref, err := eng.ScanGitHub(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan Github: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case githubExperimentalScan.FullCommand():
- cfg := sources.GitHubExperimentalConfig{
- Token: *githubExperimentalToken,
- Repository: *githubExperimentalRepo,
- ObjectDiscovery: *githubExperimentalObjectDiscovery,
- CollisionThreshold: *githubExperimentalCollisionThreshold,
- DeleteCachedData: *githubExperimentalDeleteCache,
- }
- if ref, err := eng.ScanGitHubExperimental(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan using Github Experimental: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case gitlabScan.FullCommand():
- gitCloneTempPath = *gitlabClonePath
- filter, err := common.FilterFromFiles(*gitlabScanIncludePaths, *gitlabScanExcludePaths)
- if err != nil {
- return scanMetrics, fmt.Errorf("could not create filter: %v", err)
- }
-
- if len(*gitlabScanRepos) > 0 && len(*gitlabScanGroupIds) > 0 {
- return scanMetrics, fmt.Errorf("invalid config: you cannot specify both repositories and groups at the same time")
- }
-
- if err := validateClonePath(*gitlabClonePath, *gitlabNoCleanup); err != nil {
- return scanMetrics, err
- }
-
- cfg := sources.GitlabConfig{
- Endpoint: *gitlabScanEndpoint,
- Token: *gitlabScanToken,
- Repos: *gitlabScanRepos,
- GroupIds: *gitlabScanGroupIds,
- IncludeRepos: *gitlabScanIncludeRepos,
- ExcludeRepos: *gitlabScanExcludeRepos,
- Filter: filter,
- AuthInUrl: *gitlabAuthInUrl,
- ClonePath: *gitlabClonePath,
- NoCleanup: *gitlabNoCleanup,
- PrintLegacyJSON: *jsonLegacy,
- }
-
- if ref, err := eng.ScanGitLab(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan GitLab: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case filesystemScan.FullCommand():
- if len(*filesystemDirectories) > 0 {
- ctx.Logger().Info("--directory flag is deprecated, please pass directories as arguments")
- }
- paths := make([]string, 0, len(*filesystemPaths)+len(*filesystemDirectories))
- paths = append(paths, *filesystemPaths...)
- paths = append(paths, *filesystemDirectories...)
- cfg := sources.FilesystemConfig{
- Paths: paths,
- IncludePathsFile: *filesystemScanIncludePaths,
- ExcludePathsFile: *filesystemScanExcludePaths,
- }
- if ref, err := eng.ScanFileSystem(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan filesystem: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case s3Scan.FullCommand():
- cfg := sources.S3Config{
- Key: *s3ScanKey,
- Secret: *s3ScanSecret,
- SessionToken: *s3ScanSessionToken,
- Buckets: *s3ScanBuckets,
- IgnoreBuckets: *s3ScanIgnoreBuckets,
- Roles: *s3ScanRoleArns,
- CloudCred: *s3ScanCloudEnv,
- MaxObjectSize: int64(*s3ScanMaxObjectSize),
- }
- if ref, err := eng.ScanS3(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan S3: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case syslogScan.FullCommand():
- cfg := sources.SyslogConfig{
- Address: *syslogAddress,
- Format: *syslogFormat,
- Protocol: *syslogProtocol,
- CertPath: *syslogTLSCert,
- KeyPath: *syslogTLSKey,
- Concurrency: *concurrency,
- }
- if ref, err := eng.ScanSyslog(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan syslog: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case circleCiScan.FullCommand():
- if ref, err := eng.ScanCircleCI(ctx, *circleCiScanToken); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan CircleCI: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case travisCiScan.FullCommand():
- if ref, err := eng.ScanTravisCI(ctx, *travisCiScanToken); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan TravisCI: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case gcsScan.FullCommand():
- cfg := sources.GCSConfig{
- ProjectID: *gcsProjectID,
- CloudCred: *gcsCloudEnv,
- ServiceAccount: *gcsServiceAccount,
- WithoutAuth: *gcsWithoutAuth,
- ApiKey: *gcsAPIKey,
- IncludeBuckets: commaSeparatedToSlice(*gcsIncludeBuckets),
- ExcludeBuckets: commaSeparatedToSlice(*gcsExcludeBuckets),
- IncludeObjects: commaSeparatedToSlice(*gcsIncludeObjects),
- ExcludeObjects: commaSeparatedToSlice(*gcsExcludeObjects),
- Concurrency: *concurrency,
- MaxObjectSize: int64(*gcsMaxObjectSize),
- }
- if ref, err := eng.ScanGCS(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan GCS: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case dockerScan.FullCommand():
- if *dockerScanImages != nil && *dockerScanNamespace != "" {
- return scanMetrics, fmt.Errorf("invalid config: you cannot specify both images and namespace at the same time")
- }
-
- if *dockerScanImages == nil && *dockerScanNamespace == "" {
- return scanMetrics, fmt.Errorf("invalid config: both images and namespace cannot be empty; one is required")
- }
-
- if *dockerScanRegistryToken != "" && *dockerScanNamespace == "" {
- return scanMetrics, fmt.Errorf("invalid config: registry token can only be used with registry namespace")
- }
-
- cfg := sources.DockerConfig{
- BearerToken: *dockerScanToken,
- Images: *dockerScanImages,
- UseDockerKeychain: *dockerScanToken == "",
- ExcludePaths: strings.Split(*dockerExcludePaths, ","),
- Namespace: *dockerScanNamespace,
- RegistryToken: *dockerScanRegistryToken,
- }
- if ref, err := eng.ScanDocker(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan Docker: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case postmanScan.FullCommand():
- // handle deprecated flag
- workspaceIDs := make([]string, 0, len(*postmanWorkspaceIDs)+len(*postmanWorkspaces))
- workspaceIDs = append(workspaceIDs, *postmanWorkspaceIDs...)
- workspaceIDs = append(workspaceIDs, *postmanWorkspaces...)
-
- // handle deprecated flag
- collectionIDs := make([]string, 0, len(*postmanCollectionIDs)+len(*postmanCollections))
- collectionIDs = append(collectionIDs, *postmanCollectionIDs...)
- collectionIDs = append(collectionIDs, *postmanCollections...)
-
- // handle deprecated flag
- includeCollectionIDs := make([]string, 0, len(*postmanIncludeCollectionIDs)+len(*postmanIncludeCollections))
- includeCollectionIDs = append(includeCollectionIDs, *postmanIncludeCollectionIDs...)
- includeCollectionIDs = append(includeCollectionIDs, *postmanIncludeCollections...)
-
- // handle deprecated flag
- excludeCollectionIDs := make([]string, 0, len(*postmanExcludeCollectionIDs)+len(*postmanExcludeCollections))
- excludeCollectionIDs = append(excludeCollectionIDs, *postmanExcludeCollectionIDs...)
- excludeCollectionIDs = append(excludeCollectionIDs, *postmanExcludeCollections...)
-
- cfg := sources.PostmanConfig{
- Token: *postmanToken,
- Workspaces: workspaceIDs,
- Collections: collectionIDs,
- Environments: *postmanEnvironments,
- IncludeCollections: includeCollectionIDs,
- IncludeEnvironments: *postmanIncludeEnvironments,
- ExcludeCollections: excludeCollectionIDs,
- ExcludeEnvironments: *postmanExcludeEnvironments,
- CollectionPaths: *postmanCollectionPaths,
- WorkspacePaths: *postmanWorkspacePaths,
- EnvironmentPaths: *postmanEnvironmentPaths,
- }
- if ref, err := eng.ScanPostman(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan Postman: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case elasticsearchScan.FullCommand():
- cfg := sources.ElasticsearchConfig{
- Nodes: *elasticsearchNodes,
- Username: *elasticsearchUsername,
- Password: *elasticsearchPassword,
- CloudID: *elasticsearchCloudId,
- APIKey: *elasticsearchAPIKey,
- ServiceToken: *elasticsearchServiceToken,
- IndexPattern: *elasticsearchIndexPattern,
- QueryJSON: *elasticsearchQueryJSON,
- SinceTimestamp: *elasticsearchSinceTimestamp,
- BestEffortScan: *elasticsearchBestEffortScan,
- }
- if ref, err := eng.ScanElasticsearch(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan Elasticsearch: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case jenkinsScan.FullCommand():
- cfg := engine.JenkinsConfig{
- Endpoint: *jenkinsURL,
- InsecureSkipVerifyTLS: *jenkinsInsecureSkipVerifyTLS,
- Username: *jenkinsUsername,
- Password: *jenkinsPassword,
- }
- if ref, err := eng.ScanJenkins(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan Jenkins: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case huggingfaceScan.FullCommand():
- if *huggingfaceEndpoint != "" {
- *huggingfaceEndpoint = strings.TrimRight(*huggingfaceEndpoint, "/")
- }
-
- if len(*huggingfaceModels) == 0 && len(*huggingfaceSpaces) == 0 && len(*huggingfaceDatasets) == 0 && len(*huggingfaceOrgs) == 0 && len(*huggingfaceUsers) == 0 {
- return scanMetrics, fmt.Errorf("invalid config: you must specify at least one organization, user, model, space or dataset")
- }
-
- cfg := engine.HuggingfaceConfig{
- Endpoint: *huggingfaceEndpoint,
- Models: *huggingfaceModels,
- Spaces: *huggingfaceSpaces,
- Datasets: *huggingfaceDatasets,
- Organizations: *huggingfaceOrgs,
- Users: *huggingfaceUsers,
- Token: *huggingfaceToken,
- IncludeModels: *huggingfaceIncludeModels,
- IncludeSpaces: *huggingfaceIncludeSpaces,
- IncludeDatasets: *huggingfaceIncludeDatasets,
- IgnoreModels: *huggingfaceIgnoreModels,
- IgnoreSpaces: *huggingfaceIgnoreSpaces,
- IgnoreDatasets: *huggingfaceIgnoreDatasets,
- SkipAllModels: *huggingfaceSkipAllModels,
- SkipAllSpaces: *huggingfaceSkipAllSpaces,
- SkipAllDatasets: *huggingfaceSkipAllDatasets,
- IncludeDiscussions: *huggingfaceIncludeDiscussions,
- IncludePrs: *huggingfaceIncludePrs,
- Concurrency: *concurrency,
- }
- if ref, err := eng.ScanHuggingface(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan HuggingFace: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- case multiScanScan.FullCommand():
- if *configFilename == "" {
- return scanMetrics, fmt.Errorf("missing required flag: --config")
- }
- if rs, err := eng.ScanConfig(ctx, cfg.ConfiguredSources...); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan via config: %w", err)
- } else {
- refs = rs
- }
- case stdinInputScan.FullCommand():
- cfg := sources.StdinConfig{}
- if ref, err := eng.ScanStdinInput(ctx, cfg); err != nil {
- return scanMetrics, fmt.Errorf("failed to scan stdin input: %v", err)
- } else {
- refs = []sources.JobProgressRef{ref}
- }
- default:
- return scanMetrics, fmt.Errorf("invalid command: %s", cmd)
- }
-
- // Wait for all workers to finish.
- if err = eng.Finish(ctx); err != nil {
- return scanMetrics, fmt.Errorf("engine failed to finish execution: %v", err)
- }
-
- // Print any non-fatal errors reported during the scan.
- var retErr error
- for _, ref := range refs {
- if errs := ref.Snapshot().Errors; len(errs) > 0 {
- if *failOnScanErrors {
- retErr = fmt.Errorf("encountered errors during scan")
- }
- errMsgs := make([]string, len(errs))
- for i := 0; i < len(errs); i++ {
- errMsgs[i] = errs[i].Error()
- }
- ctx.Logger().Error(nil, "encountered errors during scan",
- "job", ref.JobID,
- "source_name", ref.SourceName,
- "errors", errMsgs,
- )
- }
- }
-
- if *printAvgDetectorTime {
- printAverageDetectorTime(eng)
- }
-
- return metrics{Metrics: eng.GetMetrics(), hasFoundResults: eng.HasFoundResults()}, retErr
-}
-
-// parseResults ensures that users provide valid CSV input to `--results`.
-//
-// This is a work-around to kingpin not supporting CSVs.
-// See: https://github.com/trufflesecurity/trufflehog/pull/2372#issuecomment-1983868917
-func parseResults(input *string) (map[string]struct{}, error) {
- if *input == "" {
- return nil, nil
- }
-
- var (
- values = strings.Split(strings.ToLower(*input), ",")
- results = make(map[string]struct{}, 3)
- )
- for _, value := range values {
- switch value {
- case "verified", "unknown", "unverified", "filtered_unverified":
- results[value] = struct{}{}
- default:
- return nil, fmt.Errorf("invalid value '%s', valid values are 'verified,unknown,unverified,filtered_unverified'", value)
- }
- }
- return results, nil
-}
-
-// logFatalFunc returns a log.Fatal style function. Calling the returned
-// function will terminate the program without cleanup.
-func logFatalFunc(logger logr.Logger) func(error, string, ...any) {
- return func(err error, message string, keyAndVals ...any) {
- logger.Error(err, message, keyAndVals...)
- if err != nil {
- os.Exit(1)
- return
- }
- os.Exit(0)
- }
-}
-
-func commaSeparatedToSlice(s []string) []string {
- var result []string
- for _, items := range s {
- for _, item := range strings.Split(items, ",") {
- item = strings.TrimSpace(item)
- if item == "" {
- continue
- }
- result = append(result, item)
- }
- }
- return result
-}
-
-func printAverageDetectorTime(e *engine.Engine) {
- fmt.Fprintln(
- os.Stderr,
- "Average detector time is the measurement of average time spent on each detector when results are returned.",
- )
- for detectorName, duration := range e.GetDetectorsMetrics() {
- fmt.Fprintf(os.Stderr, "%s: %s\n", detectorName, duration)
- }
-}
-
-// validateClonePath ensures that --clone-path, if provided, exists and is a directory.
-// It also verifies that --no-cleanup is only allowed when --clone-path is set.
-// Note: without a custom clone path, repositories are cloned into temporary directories, which should never be retained.
-func validateClonePath(clonePath string, noCleanup bool) error {
- if noCleanup && clonePath == "" {
- return fmt.Errorf("invalid configuration: --no-cleanup can only be used together with --clone-path")
- }
-
- if clonePath == "" {
- return nil
- }
-
- info, err := os.Stat(clonePath)
- if err != nil {
- if os.IsNotExist(err) {
- return fmt.Errorf("path provided to --clone-path: %q does not exist", clonePath)
- }
-
- return fmt.Errorf("failed to access --clone-path %q: %w", clonePath, err)
- }
-
- if !info.IsDir() {
- return fmt.Errorf("path provided to --clone-path: %q is not a directory", clonePath)
- }
-
- return nil
-}
diff --git a/pkg/analyzer/README.md b/pkg/analyzer/README.md
deleted file mode 100644
index a7efc5522250..000000000000
--- a/pkg/analyzer/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# Implementing Analyzers
-
-## Defining the Permissions
-
-Permissions can be defined in:
-- lower snake case as `permission_name:access_level`
-- kebab case as `permission-name:read`
-- dot notation as `permission.name:read`
-
-The Permissions are initially defined as a [yaml file](analyzers/twilio/permissions.yaml).
-
-At the top of the [analyzer implementation](analyzers/twilio/twilio.go) you specify the go generate command.
-
-You can install the generator with `go install github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/generate_permissions`.
-
-Then you can run `go generate ./...` to generate the Permission types for the analyzer.
-
-The generated Permission types are to be used in the `AnalyzerResult` struct when defining the `Permissions` and in your code.
diff --git a/pkg/analyzer/analyzers/airbrake/airbrake.go b/pkg/analyzer/analyzers/airbrake/airbrake.go
deleted file mode 100644
index 00b8c8cebe0a..000000000000
--- a/pkg/analyzer/analyzers/airbrake/airbrake.go
+++ /dev/null
@@ -1,206 +0,0 @@
-package airbrake
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "os"
- "strconv"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeAirbrake }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- info, err := AnalyzePermissions(a.Cfg, credInfo["key"])
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- Metadata: map[string]any{
- "key_type": info.KeyType,
- "reference": info.Reference,
- },
- }
- // Copy the rest of the metadata over.
- for k, v := range info.Misc {
- result.Metadata[k] = v
- }
-
- // Build a list of Bindings by referencing the same permissions list
- // for each resource.
- permissions := allPermissions()
- for _, proj := range info.Projects {
- resource := analyzers.Resource{
- Name: proj.Name,
- FullyQualifiedName: strconv.Itoa(proj.ID),
- Type: "project",
- }
- for _, perm := range permissions {
- binding := analyzers.Binding{
- Resource: resource,
- Permission: perm,
- }
- result.Bindings = append(result.Bindings, binding)
- }
- }
-
- return &result
-}
-
-type SecretInfo struct {
- KeyType string
- Projects []Project
- Reference string
- Scopes []analyzers.Permission
- Misc map[string]string
-}
-
-type Project struct {
- Name string `json:"name"`
- ID int `json:"id"`
-}
-
-// validateKey checks if the key is valid and returns the projects associated with the key
-func validateKey(cfg *config.Config, key string) (bool, []Project, error) {
- type ProjectsJSON struct {
- Projects []Project `json:"projects"`
- }
- // create struct to hold response
- var projects ProjectsJSON
-
- // create http client
- client := analyzers.NewAnalyzeClient(cfg)
-
- // create request
- req, err := http.NewRequest("GET", "https://api.airbrake.io/api/v4/projects", nil)
- if err != nil {
- return false, projects.Projects, err
- }
-
- // add key as url param
- q := req.URL.Query()
- q.Add("key", key)
- req.URL.RawQuery = q.Encode()
-
- // send request
- resp, err := client.Do(req)
- if err != nil {
- return false, projects.Projects, err
- }
-
- // read response
- defer resp.Body.Close()
-
- // if status code is 200, decode response
- if resp.StatusCode == 200 {
- err := json.NewDecoder(resp.Body).Decode(&projects)
- return true, projects.Projects, err
- }
-
- // if status code is not 200, return false
- return false, projects.Projects, nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] %s", err.Error())
- return
- }
-
- color.Green("[!] Valid Airbrake User API Key\n\n")
- color.Green("[i] Key Type: " + info.KeyType)
- if v, ok := info.Misc["expiration"]; ok {
- color.Green("[i] Expiration: %s", v)
- }
- if v, ok := info.Misc["duration"]; ok {
- color.Green("[i] Duration: %s", v)
- }
-
- color.Green("\n[i] Projects:")
- printProjects(info.Projects...)
-
- color.Green("\n[i] Permissions:")
- printPermissions(info.Scopes)
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- valid, projects, err := validateKey(cfg, key)
- if err != nil {
- return nil, err
- }
- if !valid {
- return nil, fmt.Errorf("Invalid Airbrake User API Key")
- }
-
- info := &SecretInfo{
- Projects: projects,
- Reference: "https://docs.airbrake.io/docs/devops-tools/api/",
- // If the token exists, it has all permissions.
- Scopes: allPermissions(),
- Misc: make(map[string]string),
- }
- if len(key) == 40 {
- info.KeyType = "User Key"
- info.Misc["expiration"] = "Never"
- } else {
- info.KeyType = "User Token"
- info.Misc["duration"] = "Short Lived"
- }
- return info, nil
-}
-
-func allPermissions() []analyzers.Permission {
- permissions := make([]analyzers.Permission, len(scope_order))
- for i, perm := range scope_order {
- permissions[i] = analyzers.Permission{Value: perm}
- }
- return permissions
-}
-
-func printProjects(projects ...Project) {
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Project ID", "Project Name"})
- for _, project := range projects {
- t.AppendRow([]any{color.GreenString("%d", project.ID), color.GreenString("%s", project.Name)})
- }
- t.Render()
-}
-
-func printPermissions(scopes []analyzers.Permission) {
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Scope", "Permissions"})
- for _, scope := range scopes {
- scope := scope.Value
- for i, permission := range scope_mapping[scope] {
- if i == 0 {
- t.AppendRow([]any{color.GreenString("%s", scope), color.GreenString("%s", permission)})
- continue
- }
- }
- }
- t.Render()
- fmt.Println("| Ref: https://docs.airbrake.io/docs/devops-tools/api/ |")
- fmt.Println("+------------------------+---------------------------------+")
-}
diff --git a/pkg/analyzer/analyzers/airbrake/scopes.go b/pkg/analyzer/analyzers/airbrake/scopes.go
deleted file mode 100644
index 7b1f6b6a2b90..000000000000
--- a/pkg/analyzer/analyzers/airbrake/scopes.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package airbrake
-
-var scope_order = []string{
- "Authentication",
- "Performance Monitoring",
- "Error Notification",
- "Projects",
- "Deploys",
- "Groups",
- "Notices",
- "Project Activities",
- "Source Maps",
- "iOS Crash Reports",
-}
-
-var scope_mapping = map[string][]string{
- "Authentication": {"Create user token"},
- "Performance Monitoring": {"Route performance endpoint", "Routes breakdown endpoint", "Database query stats", "Queue stats"},
- "Error Notification": {"Create notice"},
- "Projects": {"List projects", "Show projects"},
- "Deploys": {"Create deploy", "List deploys", "Show deploy"},
- "Groups": {"List groups", "Show group", "Mute group", "Unmute group", "Delete group", "List groups across all projects", "Show group statistics"},
- "Notices": {"List notices", "Show notice status"},
- "Project Activities": {"List project activities", "Show project statistics"},
- "Source Maps": {"Create source map", "List source maps", "Show source map", "Delete source map"},
- "iOS Crash Reports": {"Create iOS crash report"},
-}
diff --git a/pkg/analyzer/analyzers/airtable/airtableoauth/airtable.go b/pkg/analyzer/analyzers/airtable/airtableoauth/airtable.go
deleted file mode 100644
index d54c589c2f55..000000000000
--- a/pkg/analyzer/analyzers/airtable/airtableoauth/airtable.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package airtableoauth
-
-import (
- "errors"
-
- "github.com/fatih/color"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airtable/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeAirtableOAuth }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- token, ok := credInfo["token"]
- if !ok {
- return nil, errors.New("token not found in credInfo")
- }
-
- userInfo, err := common.FetchAirtableUserInfo(token)
- if err != nil {
- return nil, err
- }
-
- var basesInfo *common.AirtableBases
- baseScope := common.PermissionStrings[common.SchemaBasesRead]
- if hasScope(userInfo.Scopes, baseScope) {
- basesInfo, _ = common.FetchAirtableBases(token)
- }
-
- return common.MapToAnalyzerResult(userInfo, basesInfo), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, token string) {
- userInfo, err := common.FetchAirtableUserInfo(token)
- if err != nil {
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- color.Green("[!] Valid Airtable OAuth2 Access Token\n\n")
- printUserAndPermissions(userInfo)
-
- baseScope := common.PermissionStrings[common.SchemaBasesRead]
- if hasScope(userInfo.Scopes, baseScope) {
- var basesInfo *common.AirtableBases
- basesInfo, _ = common.FetchAirtableBases(token)
- common.PrintBases(basesInfo)
- }
-}
-
-func hasScope(scopes []string, target string) bool {
- for _, scope := range scopes {
- if scope == target {
- return true
- }
- }
- return false
-}
-
-func printUserAndPermissions(info *common.AirtableUserInfo) {
- scopeStatusMap := make(map[string]bool)
- for _, scope := range common.PermissionStrings {
- scopeStatusMap[scope] = false
- }
- for _, scope := range info.Scopes {
- scopeStatusMap[scope] = true
- }
-
- common.PrintUserAndPermissions(info, scopeStatusMap)
-}
diff --git a/pkg/analyzer/analyzers/airtable/airtableoauth/airtable_test.go b/pkg/analyzer/analyzers/airtable/airtableoauth/airtable_test.go
deleted file mode 100644
index 402cbf5acfcc..000000000000
--- a/pkg/analyzer/analyzers/airtable/airtableoauth/airtable_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package airtableoauth
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- token string
- want string // JSON string
- wantErr bool
- }{
- {
- token: testSecrets.MustGetField("AIRTABLEOAUTH_TOKEN"),
- name: "valid Airtable OAuth Token",
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"token": tt.token})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/airtable/airtableoauth/expected_output.json b/pkg/analyzer/analyzers/airtable/airtableoauth/expected_output.json
deleted file mode 100644
index 43e6fda2b2c4..000000000000
--- a/pkg/analyzer/analyzers/airtable/airtableoauth/expected_output.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "AnalyzerType": 28,
- "Bindings": [
- {
- "Resource": {
- "Name": "usraS0CjAASH3XMpU",
- "FullyQualifiedName": "usraS0CjAASH3XMpU",
- "Type": "user",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "data.records:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "usraS0CjAASH3XMpU",
- "FullyQualifiedName": "usraS0CjAASH3XMpU",
- "Type": "user",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "schema.bases:read",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": [
- {
- "Name": "Client Leads and Sales Management",
- "FullyQualifiedName": "appzRyj5Q9R9kK6cF",
- "Type": "base",
- "Parent": null
- }
- ]
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/airtable/airtablepat/airtable.go b/pkg/analyzer/analyzers/airtable/airtablepat/airtable.go
deleted file mode 100644
index 4b5fca66c28b..000000000000
--- a/pkg/analyzer/analyzers/airtable/airtablepat/airtable.go
+++ /dev/null
@@ -1,240 +0,0 @@
-package airtablepat
-
-import (
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "strings"
-
- "github.com/fatih/color"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airtable/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeAirtablePat }
-
-var scopeStatusMap = make(map[string]bool)
-
-func getEndpoint(endpointName common.EndpointName) (common.Endpoint, bool) {
- return common.GetEndpoint(endpointName)
-}
-
-func getScopeEndpoint(scope string) (common.Endpoint, bool) {
- return common.GetScopeEndpoint(scope)
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- token, ok := credInfo["token"]
- if !ok {
- return nil, errors.New("token not found in credInfo")
- }
-
- userInfo, err := common.FetchAirtableUserInfo(token)
- if err != nil {
- return nil, err
- }
-
- scopeStatusMap[common.PermissionStrings[common.UserEmailRead]] = userInfo.Email != nil
-
- var basesInfo *common.AirtableBases
- granted, err := determineScope(token, common.SchemaBasesRead, nil)
- if err != nil {
- return nil, err
- }
- if granted {
- basesInfo, err = common.FetchAirtableBases(token)
- if err != nil {
- return nil, err
- }
- // If bases are fetched, determine the token scopes
- err := determineScopes(token, basesInfo)
- if err != nil {
- return nil, err
- }
- }
-
- return mapToAnalyzerResult(userInfo, basesInfo), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, token string) {
- userInfo, err := common.FetchAirtableUserInfo(token)
- if err != nil {
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- scopeStatusMap[common.PermissionStrings[common.UserEmailRead]] = userInfo.Email != nil
-
- var basesInfo *common.AirtableBases
- basesReadPermission := common.SchemaBasesRead
- if granted, err := determineScope(token, basesReadPermission, nil); granted {
- if err != nil {
- color.Red("[x] Error : %s", err.Error())
- return
- }
- basesInfo, _ = common.FetchAirtableBases(token)
- err := determineScopes(token, basesInfo)
- if err != nil {
- color.Red("[x] Error : %s", err.Error())
- return
- }
- }
-
- color.Green("[!] Valid Airtable Personal Access Token\n\n")
-
- common.PrintUserAndPermissions(userInfo, scopeStatusMap)
- if scopeStatusMap[common.PermissionStrings[basesReadPermission]] {
- common.PrintBases(basesInfo)
- }
-}
-
-// determineScope checks whether the given token has the specified permission by making an API call.
-//
-// The function performs the following actions:
-// - Determines the appropriate API Endpoint based on the input scope/permission.
-// - Constructs an HTTP request using the endpoint's URL, method, and required IDs.
-// If the URL contains path parameters (e.g., "{baseID}"), they must be replaced using `requiredIDs`.
-// - Sends the request and analyzes the response to determine if the token has the requested permission.
-//
-// Returns `true` if the token has the permission, `false` otherwise.
-// If an error occurs, it returns false along with the encountered error.
-func determineScope(token string, perm common.Permission, requiredIDs map[string]string) (bool, error) {
- scopeString := common.PermissionStrings[perm]
- endpoint, exists := getScopeEndpoint(scopeString)
- if !exists {
- return false, nil
- }
-
- url := endpoint.URL
- if requiredIDs != nil {
- for _, key := range endpoint.RequiredIDs {
- if value, ok := requiredIDs[key]; ok {
- url = strings.Replace(url, fmt.Sprintf("{%s}", key), value, -1)
- }
- }
- }
-
- resp, err := common.CallAirtableAPI(token, endpoint.Method, url)
- if err != nil {
- return false, err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode == endpoint.ExpectedSuccessStatus {
- scopeStatusMap[scopeString] = true
- return true, nil
- }
-
- // If the response status is not 200 OK, we need to verify if the error is as expected
- if endpoint.ExpectedErrorResponse != nil {
- var result map[string]any
- if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
- return false, err
- }
-
- errorInfo, ok := result["error"].(map[string]any)
- if !ok {
- // If no error is found in the response, the scope is unverified
- return false, nil
- }
- errorType, ok := errorInfo["type"].(string)
- if !ok || errorType != endpoint.ExpectedErrorResponse.Type {
- // If "type" is missing from the error body, or mismatches the expected type, the scope is unverified
- return false, nil
- }
-
- // The token lacks the scope/permission to fulfill the request
- scopeStatusMap[scopeString] = false
- return false, nil
- }
-
- // Can not determine scope as the expected error is unknown
- return false, nil
-}
-
-func determineScopes(token string, basesInfo *common.AirtableBases) error {
- if basesInfo == nil || len(basesInfo.Bases) == 0 {
- return nil
- }
-
- for _, base := range basesInfo.Bases {
- requiredIDs := map[string]string{"baseID": base.ID}
- tableScopesDetermined := false
-
- // Verify token "webhooks:manage" permission
- _, err := determineScope(token, common.WebhookManage, requiredIDs)
- if err != nil {
- return err
- }
- // Verify token "block:manage" permission
- _, err = determineScope(token, common.BlockManage, requiredIDs)
- if err != nil {
- return err
- }
-
- if base.Schema == nil || len(base.Schema.Tables) == 0 {
- return nil
- }
-
- // Verifying scopes that require an existing table
- for _, table := range base.Schema.Tables {
- requiredIDs["tableID"] = table.ID
-
- if !tableScopesDetermined {
- _, err = determineScope(token, common.SchemaBasesWrite, requiredIDs)
- if err != nil {
- return err
- }
- _, err = determineScope(token, common.DataRecordsWrite, requiredIDs)
- if err != nil {
- return err
- }
- tableScopesDetermined = true
- }
-
- granted, err := determineScope(token, common.DataRecordsRead, requiredIDs)
- if err != nil {
- return err
- }
- if !granted {
- continue
- }
- // Verifying scopes that require an existing "record" and the "data records read" permission
- records, err := fetchAirtableRecords(token, base.ID, table.ID)
- if err != nil {
- return err
- }
- for _, record := range records {
- requiredIDs["recordID"] = record.ID
- _, err = determineScope(token, common.DataRecordcommentsRead, requiredIDs)
- if err != nil {
- return err
- }
- break
- }
- if len(records) != 0 {
- break
- }
- }
- }
- return nil
-}
-
-func mapToAnalyzerResult(userInfo *common.AirtableUserInfo, basesInfo *common.AirtableBases) *analyzers.AnalyzerResult {
- for scope, status := range scopeStatusMap {
- if status {
- userInfo.Scopes = append(userInfo.Scopes, scope)
- }
- }
- return common.MapToAnalyzerResult(userInfo, basesInfo)
-}
diff --git a/pkg/analyzer/analyzers/airtable/airtablepat/airtable_test.go b/pkg/analyzer/analyzers/airtable/airtablepat/airtable_test.go
deleted file mode 100644
index 33cd6f74e560..000000000000
--- a/pkg/analyzer/analyzers/airtable/airtablepat/airtable_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package airtablepat
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- token string
- want string // JSON string
- wantErr bool
- }{
- {
- token: testSecrets.MustGetField("AIRTABLEOAUTH_TOKEN"),
- name: "valid Airtable Personal Access Token",
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"token": tt.token})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/airtable/airtablepat/expected_output.json b/pkg/analyzer/analyzers/airtable/airtablepat/expected_output.json
deleted file mode 100644
index e5b39db5639f..000000000000
--- a/pkg/analyzer/analyzers/airtable/airtablepat/expected_output.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "AnalyzerType": 29,
- "Bindings": [
- {
- "Resource": {
- "Name": "usraS0CjAASH3XMpU",
- "FullyQualifiedName": "usraS0CjAASH3XMpU",
- "Type": "user",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "data.records:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "usraS0CjAASH3XMpU",
- "FullyQualifiedName": "usraS0CjAASH3XMpU",
- "Type": "user",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "schema.bases:read",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": [
- {
- "Name": "Client Leads and Sales Management",
- "FullyQualifiedName": "appzRyj5Q9R9kK6cF",
- "Type": "base",
- "Parent": null
- }
- ]
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/airtable/airtablepat/requests.go b/pkg/analyzer/analyzers/airtable/airtablepat/requests.go
deleted file mode 100644
index 2afac070e646..000000000000
--- a/pkg/analyzer/analyzers/airtable/airtablepat/requests.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package airtablepat
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airtable/common"
-)
-
-type AirtableRecordsResponse struct {
- Records []common.AirtableEntity `json:"records"`
-}
-
-func fetchAirtableRecords(token string, baseID string, tableID string) ([]common.AirtableEntity, error) {
- endpoint, exists := getEndpoint(common.ListRecordsEndpoint)
- if !exists {
- return nil, fmt.Errorf("endpoint for ListRecordsEndpoint does not exist")
- }
- url := strings.Replace(strings.Replace(endpoint.URL, "{baseID}", baseID, -1), "{tableID}", tableID, -1)
- resp, err := common.CallAirtableAPI(token, "GET", url)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("failed to fetch Airtable records, status: %d", resp.StatusCode)
- }
-
- var recordsResponse AirtableRecordsResponse
- if err := json.NewDecoder(resp.Body).Decode(&recordsResponse); err != nil {
- return nil, err
- }
-
- return recordsResponse.Records, nil
-}
diff --git a/pkg/analyzer/analyzers/airtable/common/common.go b/pkg/analyzer/analyzers/airtable/common/common.go
deleted file mode 100644
index cf989ca84fe0..000000000000
--- a/pkg/analyzer/analyzers/airtable/common/common.go
+++ /dev/null
@@ -1,209 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go common
-package common
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "os"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-)
-
-func CallAirtableAPI(token string, method string, url string) (*http.Response, error) {
- req, err := http.NewRequest(method, url, nil)
- if err != nil {
- return nil, err
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
-
- resp, err := detectors.DetectorHttpClientWithNoLocalAddresses.Do(req)
- if err != nil {
- return nil, err
- }
-
- return resp, nil
-}
-
-func FetchAirtableUserInfo(token string) (*AirtableUserInfo, error) {
- endpoint, exists := GetEndpoint(GetUserInfoEndpoint)
- if !exists {
- return nil, fmt.Errorf("endpoint for GetUserInfoEndpoint does not exist")
- }
- resp, err := CallAirtableAPI(token, endpoint.Method, endpoint.URL)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("failed to fetch Airtable user info, status: %d", resp.StatusCode)
- }
-
- var userInfo AirtableUserInfo
- if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
- return nil, err
- }
-
- return &userInfo, nil
-}
-
-func FetchAirtableBases(token string) (*AirtableBases, error) {
- endpoint, exists := GetEndpoint(ListBasesEndpoint)
- if !exists {
- return nil, fmt.Errorf("endpoint for ListBasesEndpoint does not exist")
- }
- resp, err := CallAirtableAPI(token, endpoint.Method, endpoint.URL)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("failed to fetch Airtable bases, status: %d", resp.StatusCode)
- }
-
- var basesInfo AirtableBases
- if err := json.NewDecoder(resp.Body).Decode(&basesInfo); err != nil {
- return nil, err
- }
-
- // Fetch schema for each base
- for i, base := range basesInfo.Bases {
- schema, err := fetchBaseSchema(token, base.ID)
- if err != nil {
- basesInfo.Bases[i].Schema = nil
- } else {
- basesInfo.Bases[i].Schema = schema
- }
- }
-
- return &basesInfo, nil
-}
-
-func fetchBaseSchema(token string, baseID string) (*Schema, error) {
- endpoint, exists := GetEndpoint(GetBaseSchemaEndpoint)
- if !exists {
- return nil, fmt.Errorf("endpoint for GetBaseSchemaEndpoint does not exist")
- }
- url := strings.ReplaceAll(endpoint.URL, "{baseID}", baseID)
- resp, err := CallAirtableAPI(token, endpoint.Method, url)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("failed to fetch schema for base %s, status: %d", baseID, resp.StatusCode)
- }
-
- var schema Schema
- if err := json.NewDecoder(resp.Body).Decode(&schema); err != nil {
- return nil, err
- }
-
- return &schema, nil
-}
-
-func MapToAnalyzerResult(userInfo *AirtableUserInfo, basesInfo *AirtableBases) *analyzers.AnalyzerResult {
- if userInfo == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeAirtableOAuth,
- }
- var permissions []analyzers.Permission
- for _, scope := range userInfo.Scopes {
- permissions = append(permissions, analyzers.Permission{Value: scope})
- }
- userResource := analyzers.Resource{
- Name: userInfo.ID,
- FullyQualifiedName: userInfo.ID,
- Type: "user",
- Metadata: map[string]any{},
- }
-
- if userInfo.Email != nil {
- userResource.Metadata["email"] = *userInfo.Email
- }
-
- result.Bindings = analyzers.BindAllPermissions(userResource, permissions...)
-
- if basesInfo != nil {
- for _, base := range basesInfo.Bases {
- resource := analyzers.Resource{
- Name: base.Name,
- FullyQualifiedName: base.ID,
- Type: "base",
- }
- result.UnboundedResources = append(result.UnboundedResources, resource)
- }
- }
-
- return &result
-}
-
-func PrintUserAndPermissions(info *AirtableUserInfo, scopeStatusMap map[string]bool) {
- color.Yellow("[i] User:")
- t1 := table.NewWriter()
- email := "N/A"
- if info.Email != nil {
- email = *info.Email
- }
- t1.SetOutputMirror(os.Stdout)
- t1.AppendHeader(table.Row{"ID", "Email"})
- t1.AppendRow(table.Row{color.GreenString(info.ID), color.GreenString(email)})
- t1.SetOutputMirror(os.Stdout)
- t1.Render()
-
- color.Yellow("\n[i] Scopes:")
- t2 := table.NewWriter()
- t2.SetOutputMirror(os.Stdout)
- t2.AppendHeader(table.Row{"Scope", "Permission", "Status"})
- for _, scope := range PermissionStrings {
- scopeStatus := "Could not verify"
- if status, ok := scopeStatusMap[scope]; ok {
- if status {
- scopeStatus = "Granted"
- } else {
- scopeStatus = "Denied"
- }
- }
- permissions, ok := GetScopePermissions(scope)
- if !ok {
- continue
- }
- for i, permission := range permissions {
- scopeString := ""
- if i == 0 {
- scopeString = scope
- }
- t2.AppendRow(table.Row{color.GreenString(scopeString), color.GreenString(permission), color.GreenString(scopeStatus)})
- scopeStatus = ""
- }
- t2.AppendSeparator()
- }
- t2.Render()
- fmt.Printf("%s: https://airtable.com/developers/web/api/scopes\n", color.GreenString("Ref"))
-}
-
-func PrintBases(bases *AirtableBases) {
- color.Yellow("\n[i] Bases:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- if len(bases.Bases) > 0 {
- t.AppendHeader(table.Row{"ID", "Name"})
- for _, base := range bases.Bases {
- t.AppendRow(table.Row{color.GreenString(base.ID), color.GreenString(base.Name)})
- }
- } else {
- fmt.Printf("%s\n", color.GreenString("No bases associated with token"))
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/airtable/common/endpoints.go b/pkg/analyzer/analyzers/airtable/common/endpoints.go
deleted file mode 100644
index 13e7e7115982..000000000000
--- a/pkg/analyzer/analyzers/airtable/common/endpoints.go
+++ /dev/null
@@ -1,133 +0,0 @@
-package common
-
-import "net/http"
-
-type ErrorResponse struct {
- Type string
-}
-
-type Endpoint struct {
- URL string
- Method string
- RequiredIDs []string
- RequiredPermission *string
- ExpectedSuccessStatus int
- ExpectedErrorResponse *ErrorResponse
-}
-
-type EndpointName int
-
-const (
- GetUserInfoEndpoint EndpointName = iota
- ListBasesEndpoint EndpointName = iota
- UpdateBaseEndpoint EndpointName = iota
- GetBaseSchemaEndpoint EndpointName = iota
- ListRecordsEndpoint EndpointName = iota
- CreateRecordEndpoint EndpointName = iota
- ListRecordCommentsEndpoint EndpointName = iota
- ListWebhooksEndpoint EndpointName = iota
- ListBlockInstallationsEndpoint EndpointName = iota
-)
-
-var endpoints map[EndpointName]Endpoint
-
-func init() {
- endpoints = map[EndpointName]Endpoint{
- GetUserInfoEndpoint: {
- URL: "https://api.airtable.com/v0/meta/whoami",
- Method: "GET",
- },
- ListBasesEndpoint: {
- URL: "https://api.airtable.com/v0/meta/bases",
- Method: "GET",
- RequiredPermission: GetRequiredPermission(SchemaBasesRead),
- ExpectedSuccessStatus: http.StatusOK,
- ExpectedErrorResponse: &ErrorResponse{
- Type: "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND",
- },
- },
- UpdateBaseEndpoint: {
- URL: "https://api.airtable.com/v0/meta/bases/{baseID}/tables/{tableID}",
- Method: "PATCH",
- RequiredIDs: []string{"baseID", "tableID"},
- RequiredPermission: GetRequiredPermission(SchemaBasesWrite),
- ExpectedSuccessStatus: http.StatusUnprocessableEntity,
- ExpectedErrorResponse: &ErrorResponse{
- Type: "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND",
- },
- },
- GetBaseSchemaEndpoint: {
- URL: "https://api.airtable.com/v0/meta/bases/{baseID}/tables",
- Method: "GET",
- RequiredIDs: []string{"baseID"},
- RequiredPermission: GetRequiredPermission(SchemaBasesRead),
- ExpectedSuccessStatus: http.StatusOK,
- ExpectedErrorResponse: &ErrorResponse{
- Type: "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND",
- },
- },
- ListRecordsEndpoint: {
- URL: "https://api.airtable.com/v0/{baseID}/{tableID}",
- Method: "GET",
- RequiredIDs: []string{"baseID", "tableID"},
- RequiredPermission: GetRequiredPermission(DataRecordsRead),
- ExpectedSuccessStatus: http.StatusOK,
- ExpectedErrorResponse: &ErrorResponse{
- Type: "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND",
- },
- },
- CreateRecordEndpoint: {
- URL: "https://api.airtable.com/v0/{baseID}/{tableID}",
- Method: "POST",
- RequiredIDs: []string{"baseID", "tableID"},
- RequiredPermission: GetRequiredPermission(DataRecordsWrite),
- ExpectedSuccessStatus: http.StatusUnprocessableEntity,
- ExpectedErrorResponse: &ErrorResponse{
- Type: "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND",
- },
- },
- ListRecordCommentsEndpoint: {
- URL: "https://api.airtable.com/v0/{baseID}/{tableID}/{recordID}/comments",
- Method: "GET",
- RequiredIDs: []string{"baseID", "tableID", "recordID"},
- RequiredPermission: GetRequiredPermission(DataRecordcommentsRead),
- ExpectedSuccessStatus: http.StatusOK,
- ExpectedErrorResponse: &ErrorResponse{
- Type: "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND",
- },
- },
- ListWebhooksEndpoint: {
- URL: "https://api.airtable.com/v0/bases/{baseID}/webhooks",
- Method: "GET",
- RequiredIDs: []string{"baseID"},
- RequiredPermission: GetRequiredPermission(WebhookManage),
- ExpectedSuccessStatus: http.StatusOK,
- ExpectedErrorResponse: &ErrorResponse{
- Type: "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND",
- },
- },
- ListBlockInstallationsEndpoint: {
- URL: "https://api.airtable.com/v0/meta/bases/{baseID}/blockInstallations",
- Method: "GET",
- RequiredIDs: []string{"baseID"},
- RequiredPermission: GetRequiredPermission(BlockManage),
- ExpectedSuccessStatus: http.StatusOK,
- ExpectedErrorResponse: &ErrorResponse{
- Type: "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND",
- },
- },
- }
-}
-
-func GetRequiredPermission(permission Permission) *string {
- if val, exists := PermissionStrings[permission]; exists {
- return &val
- }
- return nil
-}
-
-// GetEndpoint returns the endpoint object for the provided name and whether it exists
-func GetEndpoint(name EndpointName) (Endpoint, bool) {
- endpoint, exists := endpoints[name]
- return endpoint, exists
-}
diff --git a/pkg/analyzer/analyzers/airtable/common/models.go b/pkg/analyzer/analyzers/airtable/common/models.go
deleted file mode 100644
index 862489b4f191..000000000000
--- a/pkg/analyzer/analyzers/airtable/common/models.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package common
-
-type AirtableUserInfo struct {
- ID string `json:"id"`
- Email *string `json:"email,omitempty"`
- Scopes []string `json:"scopes"`
-}
-
-type AirtableBases struct {
- Bases []struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Schema *Schema `json:"schema,omitempty"`
- } `json:"bases"`
-}
-
-type Schema struct {
- Tables []AirtableEntity `json:"tables"`
-}
-
-type AirtableEntity struct {
- ID string `json:"id"`
-}
diff --git a/pkg/analyzer/analyzers/airtable/common/permissions.go b/pkg/analyzer/analyzers/airtable/common/permissions.go
deleted file mode 100644
index 45f043f2c845..000000000000
--- a/pkg/analyzer/analyzers/airtable/common/permissions.go
+++ /dev/null
@@ -1,171 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package common
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- DataRecordsRead Permission = iota
- DataRecordsWrite Permission = iota
- DataRecordcommentsRead Permission = iota
- DataRecordcommentsWrite Permission = iota
- SchemaBasesRead Permission = iota
- SchemaBasesWrite Permission = iota
- WebhookManage Permission = iota
- BlockManage Permission = iota
- UserEmailRead Permission = iota
- EnterpriseGroupsRead Permission = iota
- WorkspacesandbasesRead Permission = iota
- WorkspacesandbasesWrite Permission = iota
- WorkspacesandbasesSharesManage Permission = iota
- EnterpriseScimUsersandgroupsManage Permission = iota
- EnterpriseAuditlogsRead Permission = iota
- EnterpriseChangeeventsRead Permission = iota
- EnterpriseExportsManage Permission = iota
- EnterpriseAccountRead Permission = iota
- EnterpriseAccountWrite Permission = iota
- EnterpriseUserRead Permission = iota
- EnterpriseUserWrite Permission = iota
- EnterpriseGroupsManage Permission = iota
- WorkspacesandbasesManage Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- DataRecordsRead: "data.records:read",
- DataRecordsWrite: "data.records:write",
- DataRecordcommentsRead: "data.recordComments:read",
- DataRecordcommentsWrite: "data.recordComments:write",
- SchemaBasesRead: "schema.bases:read",
- SchemaBasesWrite: "schema.bases:write",
- WebhookManage: "webhook:manage",
- BlockManage: "block:manage",
- UserEmailRead: "user.email:read",
- EnterpriseGroupsRead: "enterprise.groups:read",
- WorkspacesandbasesRead: "workspacesAndBases:read",
- WorkspacesandbasesWrite: "workspacesAndBases:write",
- WorkspacesandbasesSharesManage: "workspacesAndBases.shares:manage",
- EnterpriseScimUsersandgroupsManage: "enterprise.scim.usersAndGroups:manage",
- EnterpriseAuditlogsRead: "enterprise.auditLogs:read",
- EnterpriseChangeeventsRead: "enterprise.changeEvents:read",
- EnterpriseExportsManage: "enterprise.exports:manage",
- EnterpriseAccountRead: "enterprise.account:read",
- EnterpriseAccountWrite: "enterprise.account:write",
- EnterpriseUserRead: "enterprise.user:read",
- EnterpriseUserWrite: "enterprise.user:write",
- EnterpriseGroupsManage: "enterprise.groups:manage",
- WorkspacesandbasesManage: "workspacesAndBases:manage",
- }
-
- StringToPermission = map[string]Permission{
- "data.records:read": DataRecordsRead,
- "data.records:write": DataRecordsWrite,
- "data.recordComments:read": DataRecordcommentsRead,
- "data.recordComments:write": DataRecordcommentsWrite,
- "schema.bases:read": SchemaBasesRead,
- "schema.bases:write": SchemaBasesWrite,
- "webhook:manage": WebhookManage,
- "block:manage": BlockManage,
- "user.email:read": UserEmailRead,
- "enterprise.groups:read": EnterpriseGroupsRead,
- "workspacesAndBases:read": WorkspacesandbasesRead,
- "workspacesAndBases:write": WorkspacesandbasesWrite,
- "workspacesAndBases.shares:manage": WorkspacesandbasesSharesManage,
- "enterprise.scim.usersAndGroups:manage": EnterpriseScimUsersandgroupsManage,
- "enterprise.auditLogs:read": EnterpriseAuditlogsRead,
- "enterprise.changeEvents:read": EnterpriseChangeeventsRead,
- "enterprise.exports:manage": EnterpriseExportsManage,
- "enterprise.account:read": EnterpriseAccountRead,
- "enterprise.account:write": EnterpriseAccountWrite,
- "enterprise.user:read": EnterpriseUserRead,
- "enterprise.user:write": EnterpriseUserWrite,
- "enterprise.groups:manage": EnterpriseGroupsManage,
- "workspacesAndBases:manage": WorkspacesandbasesManage,
- }
-
- PermissionIDs = map[Permission]int{
- DataRecordsRead: 1,
- DataRecordsWrite: 2,
- DataRecordcommentsRead: 3,
- DataRecordcommentsWrite: 4,
- SchemaBasesRead: 5,
- SchemaBasesWrite: 6,
- WebhookManage: 7,
- BlockManage: 8,
- UserEmailRead: 9,
- EnterpriseGroupsRead: 10,
- WorkspacesandbasesRead: 11,
- WorkspacesandbasesWrite: 12,
- WorkspacesandbasesSharesManage: 13,
- EnterpriseScimUsersandgroupsManage: 14,
- EnterpriseAuditlogsRead: 15,
- EnterpriseChangeeventsRead: 16,
- EnterpriseExportsManage: 17,
- EnterpriseAccountRead: 18,
- EnterpriseAccountWrite: 19,
- EnterpriseUserRead: 20,
- EnterpriseUserWrite: 21,
- EnterpriseGroupsManage: 22,
- WorkspacesandbasesManage: 23,
- }
-
- IdToPermission = map[int]Permission{
- 1: DataRecordsRead,
- 2: DataRecordsWrite,
- 3: DataRecordcommentsRead,
- 4: DataRecordcommentsWrite,
- 5: SchemaBasesRead,
- 6: SchemaBasesWrite,
- 7: WebhookManage,
- 8: BlockManage,
- 9: UserEmailRead,
- 10: EnterpriseGroupsRead,
- 11: WorkspacesandbasesRead,
- 12: WorkspacesandbasesWrite,
- 13: WorkspacesandbasesSharesManage,
- 14: EnterpriseScimUsersandgroupsManage,
- 15: EnterpriseAuditlogsRead,
- 16: EnterpriseChangeeventsRead,
- 17: EnterpriseExportsManage,
- 18: EnterpriseAccountRead,
- 19: EnterpriseAccountWrite,
- 20: EnterpriseUserRead,
- 21: EnterpriseUserWrite,
- 22: EnterpriseGroupsManage,
- 23: WorkspacesandbasesManage,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/airtable/common/permissions.yaml b/pkg/analyzer/analyzers/airtable/common/permissions.yaml
deleted file mode 100644
index a0b999417fde..000000000000
--- a/pkg/analyzer/analyzers/airtable/common/permissions.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-permissions:
- - data.records:read
- - data.records:write
- - data.recordComments:read
- - data.recordComments:write
- - schema.bases:read
- - schema.bases:write
- - webhook:manage
- - block:manage
- - user.email:read
- - enterprise.groups:read
- - workspacesAndBases:read
- - workspacesAndBases:write
- - workspacesAndBases.shares:manage
- - enterprise.scim.usersAndGroups:manage
- - enterprise.auditLogs:read
- - enterprise.changeEvents:read
- - enterprise.exports:manage
- - enterprise.account:read
- - enterprise.account:write
- - enterprise.user:read
- - enterprise.user:write
- - enterprise.groups:manage
- - workspacesAndBases:manage
diff --git a/pkg/analyzer/analyzers/airtable/common/scopes.go b/pkg/analyzer/analyzers/airtable/common/scopes.go
deleted file mode 100644
index b8ec67d0765b..000000000000
--- a/pkg/analyzer/analyzers/airtable/common/scopes.go
+++ /dev/null
@@ -1,175 +0,0 @@
-package common
-
-var scopeToPermissions = map[string][]string{
- // Basic Scopes
- "data.records:read": {
- "List records",
- "Get record",
- },
- "data.records:write": {
- "Create records",
- "Update record",
- "Update multiple records",
- "Delete record",
- "Delete multiple records",
- "Sync CSV data",
- },
- "data.recordComments:read": {
- "List comments",
- },
- "data.recordComments:write": {
- "Create comment",
- "Delete comment",
- "Update comment",
- },
- "schema.bases:read": {
- "List bases",
- "Get base schema",
- },
- "schema.bases:write": {
- "Create base",
- "Create table",
- "Update table",
- "Create field",
- "Update field",
- "Sync CSV data",
- },
- "webhook:manage": {
- "List webhooks",
- "Create a webhook",
- "Delete a webhook",
- "Enable/disable webhook notifications",
- "Refresh a webhook",
- },
- "block:manage": {
- "Create new releases and submissions for custom extensions",
- },
- "user.email:read": {
- "See the user's email address",
- },
-
- // Enterprise scopes
- "enterprise.groups:read": {
- "Get user group",
- },
- "workspacesAndBases:read": {
- "Get base collaborators",
- "List block installations",
- "Get interface",
- "List views",
- "Get view metadata",
- "Get workspace collaborators",
- },
- "workspacesAndBases:write": {
- "Delete block installation",
- "Manage block installation",
- "Add base collaborator",
- "Delete base collaborator",
- "Update collaborator base permission",
- "Add interface collaborator",
- "Delete interface collaborator",
- "Update interface collaborator",
- "Delete interface invite",
- "Delete base invite",
- "Delete view",
- "Add workspace collaborator",
- "Delete workspace collaborator",
- "Update workspace collaborator",
- "Delete workspace invite",
- "Update workspace restrictions",
- },
- "workspacesAndBases.shares:manage": {
- "List shares",
- "Delete share",
- "Manage share",
- },
- "enterprise.scim.usersAndGroups:manage": {
- "List groups",
- "Create group",
- "Delete group",
- "Get group",
- "Patch group",
- "Put group",
- "List users",
- "Create user",
- "Delete user",
- "Get user",
- "Patch user",
- "Put user",
- },
- "enterprise.auditLogs:read": {
- "Audit log events",
- "List audit log requests",
- "Create audit log request",
- "Get audit log request",
- },
- "enterprise.changeEvents:read": {
- "Change events",
- },
- "enterprise.exports:manage": {
- "List eDiscovery exports",
- "Create eDiscovery export",
- "Get eDiscovery export",
- },
- "enterprise.account:read": {
- "Get enterprise",
- },
- "enterprise.account:write": {
- "Create descendant enterprise",
- },
- "enterprise.user:read": {
- "Get users by id or email",
- "Get user by id",
- },
- "enterprise.user:write": {
- "Delete users by email",
- "Manage user batched",
- "Manage user membership",
- "Grant admin access",
- "Revoke admin access",
- "Delete user by id",
- "Manage user",
- "Logout user",
- "Remove user from enterprise",
- },
- "enterprise.groups:manage": {
- "Move user groups",
- },
- "workspacesAndBases:manage": {
- "Delete base",
- "Move workspaces",
- "Delete workspace",
- "Move base",
- },
-}
-
-var scopeToEndpointName = map[string]EndpointName{
- "schema.bases:read": ListBasesEndpoint,
- "schema.bases:write": UpdateBaseEndpoint,
- "webhook:manage": ListWebhooksEndpoint,
- "block:manage": ListBlockInstallationsEndpoint,
- "data.records:read": ListRecordsEndpoint,
- "data.records:write": CreateRecordEndpoint,
- "data.recordComments:read": ListRecordCommentsEndpoint,
-}
-
-var scopeToEndpoint map[string]Endpoint
-
-func init() {
- scopeToEndpoint = make(map[string]Endpoint)
- for scope, endpointName := range scopeToEndpointName {
- if endpoint, exists := GetEndpoint(endpointName); exists {
- scopeToEndpoint[scope] = endpoint
- }
- }
-}
-
-func GetScopePermissions(scope string) ([]string, bool) {
- permission, exists := scopeToPermissions[scope]
- return permission, exists
-}
-
-func GetScopeEndpoint(scope string) (Endpoint, bool) {
- endpoint, exists := scopeToEndpoint[scope]
- return endpoint, exists
-}
diff --git a/pkg/analyzer/analyzers/analyzers.go b/pkg/analyzer/analyzers/analyzers.go
deleted file mode 100644
index be2ad171de05..000000000000
--- a/pkg/analyzer/analyzers/analyzers.go
+++ /dev/null
@@ -1,291 +0,0 @@
-package analyzers
-
-import (
- "bytes"
- "encoding/json"
- "io"
- "net/http"
- "sort"
-
- "github.com/fatih/color"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-type (
- Analyzer interface {
- Type() AnalyzerType
- Analyze(ctx context.Context, credentialInfo map[string]string) (*AnalyzerResult, error)
- }
-
- AnalyzerType int
-
- // AnalyzerResult is the output of analysis.
- AnalyzerResult struct {
- AnalyzerType AnalyzerType
- Bindings []Binding
- UnboundedResources []Resource
- Metadata map[string]any
- }
-
- Resource struct {
- Name string
- FullyQualifiedName string
- Type string
- Metadata map[string]any
- Parent *Resource
- }
-
- Permission struct {
- Value string
- Parent *Permission
- }
-
- Binding struct {
- Resource Resource
- Permission Permission
- Condition string
- }
-)
-
-type PermissionType string
-
-const (
- READ PermissionType = "Read"
- WRITE PermissionType = "Write"
- READ_WRITE PermissionType = "Read & Write"
- NONE PermissionType = "None"
- ERROR PermissionType = "Error"
-
- FullAccess string = "full_access"
-)
-
-const (
- AnalyzerTypeInvalid AnalyzerType = iota
- AnalyzerTypeAirbrake
- AnalyzerAnthropic
- AnalyzerTypeAsana
- AnalyzerTypeBitbucket
- AnalyzerTypeDockerHub
- AnalyzerTypeElevenLabs
- AnalyzerTypeGitHub
- AnalyzerTypeGitLab
- AnalyzerTypeHuggingFace
- AnalyzerTypeMailchimp
- AnalyzerTypeMailgun
- AnalyzerTypeMySQL
- AnalyzerTypeOpenAI
- AnalyzerTypeOpsgenie
- AnalyzerTypePostgres
- AnalyzerTypePostman
- AnalyzerTypeSendgrid
- AnalyzerTypeShopify
- AnalyzerTypeSlack
- AnalyzerTypeSourcegraph
- AnalyzerTypeSquare
- AnalyzerTypeStripe
- AnalyzerTypeTwilio
- AnalyzerTypePrivateKey
- AnalyzerTypeNotion
- AnalyzerTypeDigitalOcean
- AnalyzerTypePlanetScale
- AnalyzerTypeAirtableOAuth
- AnalyzerTypeAirtablePat
- AnalyzerTypeGroq
- AnalyzerTypeLaunchDarkly
- AnalyzerTypeFigma
- AnalyzerTypePlaid
- AnalyzerTypeNetlify
- AnalyzerTypeFastly
- AnalyzerTypeMonday
- AnalyzerTypeDatadog
- AnalyzerTypeNgrok
- AnalyzerTypeMux
- AnalyzerTypePosthog
- AnalyzerTypeDropbox
- AnalyzerTypeDataBricks
- AnalyzerTypeJira
- // Add new items here with AnalyzerType prefix
-)
-
-// analyzerTypeStrings maps the enum to its string representation.
-var analyzerTypeStrings = map[AnalyzerType]string{
- AnalyzerTypeInvalid: "Invalid",
- AnalyzerTypeAirbrake: "Airbrake",
- AnalyzerAnthropic: "Anthropic",
- AnalyzerTypeAsana: "Asana",
- AnalyzerTypeBitbucket: "Bitbucket",
- AnalyzerTypeDigitalOcean: "DigitalOcean",
- AnalyzerTypeDockerHub: "DockerHub",
- AnalyzerTypeElevenLabs: "ElevenLabs",
- AnalyzerTypeGitHub: "GitHub",
- AnalyzerTypeGitLab: "GitLab",
- AnalyzerTypeHuggingFace: "HuggingFace",
- AnalyzerTypeMailchimp: "Mailchimp",
- AnalyzerTypeMailgun: "Mailgun",
- AnalyzerTypeMySQL: "MySQL",
- AnalyzerTypeOpenAI: "OpenAI",
- AnalyzerTypeOpsgenie: "Opsgenie",
- AnalyzerTypePostgres: "Postgres",
- AnalyzerTypePostman: "Postman",
- AnalyzerTypeSendgrid: "Sendgrid",
- AnalyzerTypeShopify: "Shopify",
- AnalyzerTypeSlack: "Slack",
- AnalyzerTypeSourcegraph: "Sourcegraph",
- AnalyzerTypeSquare: "Square",
- AnalyzerTypeStripe: "Stripe",
- AnalyzerTypeTwilio: "Twilio",
- AnalyzerTypePrivateKey: "PrivateKey",
- AnalyzerTypeNotion: "Notion",
- AnalyzerTypePlanetScale: "PlanetScale",
- AnalyzerTypeAirtableOAuth: "AirtableOAuth",
- AnalyzerTypeAirtablePat: "AirtablePat",
- AnalyzerTypeGroq: "Groq",
- AnalyzerTypeLaunchDarkly: "LaunchDarkly",
- AnalyzerTypeFigma: "Figma",
- AnalyzerTypePlaid: "Plaid",
- AnalyzerTypeNetlify: "Netlify",
- AnalyzerTypeFastly: "Fastly",
- AnalyzerTypeMonday: "Monday",
- AnalyzerTypeDatadog: "Datadog",
- AnalyzerTypeNgrok: "Ngrok",
- AnalyzerTypeMux: "Mux",
- AnalyzerTypePosthog: "Posthog",
- AnalyzerTypeDropbox: "Dropbox",
- AnalyzerTypeDataBricks: "DataBricks",
- AnalyzerTypeJira: "Jira",
- // Add new mappings here
-}
-
-// String method to get the string representation of an AnalyzerType.
-func (a AnalyzerType) String() string {
- if str, ok := analyzerTypeStrings[a]; ok {
- return str
- }
- return "Unknown"
-}
-
-// AvailableAnalyzers returns a sorted slice of AnalyzerType strings, skipping "Invalid".
-func AvailableAnalyzers() []string {
- var analyzerStrings []string
-
- // Iterate through the map to collect all string values except "Invalid".
- for typ, str := range analyzerTypeStrings {
- if typ != AnalyzerTypeInvalid {
- analyzerStrings = append(analyzerStrings, str)
- }
- }
-
- // Sort the slice alphabetically.
- sort.Strings(analyzerStrings)
-
- return analyzerStrings
-}
-
-type PermissionStatus struct {
- Value bool
- IsError bool
-}
-
-type HttpStatusTest struct {
- URL string
- Method string
- Payload map[string]interface{}
- Params map[string]string
- Valid []int
- Invalid []int
- Type PermissionType
- Status PermissionStatus
- Risk string
-}
-
-func (h *HttpStatusTest) RunTest(headers map[string]string) error {
- // If body data, marshal to JSON
- var data io.Reader
- if h.Payload != nil {
- jsonData, err := json.Marshal(h.Payload)
- if err != nil {
- return err
- }
- data = bytes.NewBuffer(jsonData)
- }
-
- // Create new HTTP request
- client := &http.Client{}
- req, err := http.NewRequest(h.Method, h.URL, data)
- if err != nil {
- return err
- }
-
- // Add custom headers if provided
- for key, value := range headers {
- req.Header.Set(key, value)
- }
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
-
- // Check response status code
- switch {
- case StatusContains(resp.StatusCode, h.Valid):
- h.Status.Value = true
- case StatusContains(resp.StatusCode, h.Invalid):
- h.Status.Value = false
- default:
- h.Status.IsError = true
- }
- return nil
-}
-
-type Scope struct {
- Name string
- Tests []interface{}
-}
-
-func StatusContains(status int, vals []int) bool {
- for _, v := range vals {
- if status == v {
- return true
- }
- }
- return false
-}
-
-func GetWriterFromStatus(status PermissionType) func(a ...interface{}) string {
- switch status {
- case READ:
- return color.New(color.FgYellow).SprintFunc()
- case WRITE:
- return color.New(color.FgGreen).SprintFunc()
- case READ_WRITE:
- return color.New(color.FgGreen).SprintFunc()
- case NONE:
- return color.New().SprintFunc()
- case ERROR:
- return color.New(color.FgRed).SprintFunc()
- default:
- return color.New().SprintFunc()
- }
-}
-
-var GreenWriter = color.New(color.FgGreen).SprintFunc()
-var YellowWriter = color.New(color.FgYellow).SprintFunc()
-var RedWriter = color.New(color.FgRed).SprintFunc()
-var DefaultWriter = color.New().SprintFunc()
-
-// BindAllPermissions creates a Binding for each permission to the given
-// resource.
-func BindAllPermissions(r Resource, perms ...Permission) []Binding {
- bindings := make([]Binding, len(perms))
- for i, perm := range perms {
- bindings[i] = Binding{
- Resource: r,
- Permission: perm,
- }
- }
- return bindings
-}
diff --git a/pkg/analyzer/analyzers/anthropic/anthropic.go b/pkg/analyzer/analyzers/anthropic/anthropic.go
deleted file mode 100644
index 440d6c1f338d..000000000000
--- a/pkg/analyzer/analyzers/anthropic/anthropic.go
+++ /dev/null
@@ -1,192 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go anthropic
-package anthropic
-
-import (
- "errors"
- "os"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-const (
- // Key Types
- APIKey = "API-Key"
- AdminKey = "Admin-Key"
-)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-// SecretInfo hold the information about the anthropic key
-type SecretInfo struct {
- Valid bool
- Type string // key type - TODO: Handle Anthropic Admin Keys
- AnthropicResources []AnthropicResource
- Permissions string // always full_access
- Misc map[string]string
-}
-
-// AnthropicResource is any resource that can be accessed with anthropic key
-type AnthropicResource struct {
- ID string
- Name string
- Type string
- Parent *AnthropicResource
- Metadata map[string]string
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerAnthropic
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, exist := credInfo["key"]
- if !exist {
- return nil, errors.New("key not found in credentials info")
- }
-
- secretInfo, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(secretInfo), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- // just print the error in cli and continue as a partial success
- color.Red("[x] Error : %s", err.Error())
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- if info.Valid {
- color.Green("[!] Valid Anthropic %s\n\n", info.Type)
- // no user information
- // print full access permission
- printPermission(info.Permissions)
- // print resources
- printAnthropicResources(info.AnthropicResources)
-
- color.Yellow("\n[i] Expires: Never")
- }
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- // create a HTTP client
- client := analyzers.NewAnalyzeClient(cfg)
-
- keyType := getKeyType(key)
-
- var secretInfo = &SecretInfo{
- Type: keyType,
- }
-
- switch keyType {
- case APIKey:
- if err := captureAPIKeyResources(client, key, secretInfo); err != nil {
- return nil, err
- }
- case AdminKey:
- if err := captureAdminKeyResources(client, key, secretInfo); err != nil {
- return nil, err
- }
- default:
- return nil, errors.New("unsupported key type")
- }
-
- // anthropic key has full access only
- secretInfo.Permissions = PermissionStrings[FullAccess]
- secretInfo.Valid = true
-
- return secretInfo, nil
-}
-
-// secretInfoToAnalyzerResult translate secret info to Analyzer Result
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerAnthropic,
- Metadata: map[string]any{"Valid_Key": info.Valid},
- Bindings: make([]analyzers.Binding, len(info.AnthropicResources)),
- }
-
- // extract information to create bindings and append to result bindings
- for _, Anthropicresource := range info.AnthropicResources {
- binding := analyzers.Binding{
- Resource: analyzers.Resource{
- Name: Anthropicresource.Name,
- FullyQualifiedName: Anthropicresource.ID,
- Type: Anthropicresource.Type,
- Metadata: map[string]any{},
- },
- Permission: analyzers.Permission{
- Value: info.Permissions,
- },
- }
-
- if Anthropicresource.Parent != nil {
- binding.Resource.Parent = &analyzers.Resource{
- Name: Anthropicresource.Parent.Name,
- FullyQualifiedName: Anthropicresource.Parent.ID,
- Type: Anthropicresource.Parent.Type,
- }
- }
-
- for key, value := range Anthropicresource.Metadata {
- binding.Resource.Metadata[key] = value
- }
-
- result.Bindings = append(result.Bindings, binding)
- }
-
- return &result
-}
-
-func printPermission(permission string) {
- color.Yellow("[i] Permissions:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission"})
- t.AppendRow(table.Row{color.GreenString(permission)})
- t.Render()
-}
-
-func printAnthropicResources(resources []AnthropicResource) {
- color.Green("\n[i] Resources:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Resource Type", "Resource ID", "Resource Name"})
- for _, resource := range resources {
- t.AppendRow(table.Row{color.GreenString(resource.Type), color.GreenString(resource.ID), color.GreenString(resource.Name)})
- }
- t.Render()
-}
-
-// getKeyType return the type of key
-func getKeyType(key string) string {
- if strings.Contains(key, "sk-ant-admin01") {
- return AdminKey
- } else if strings.Contains(key, "sk-ant-api03") {
- return APIKey
- }
-
- return ""
-}
diff --git a/pkg/analyzer/analyzers/anthropic/anthropic_test.go b/pkg/analyzer/analyzers/anthropic/anthropic_test.go
deleted file mode 100644
index 19dd2d326d46..000000000000
--- a/pkg/analyzer/analyzers/anthropic/anthropic_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package anthropic
-
-import (
- _ "embed"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed result_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- secret := testSecrets.MustGetField("ANTHROPIC")
-
- tests := []struct {
- name string
- secret string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid anthropic key",
- secret: secret,
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.secret})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/anthropic/permissions.go b/pkg/analyzer/analyzers/anthropic/permissions.go
deleted file mode 100644
index 225a54c8cea9..000000000000
--- a/pkg/analyzer/analyzers/anthropic/permissions.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package anthropic
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- FullAccess Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- FullAccess: "full_access",
- }
-
- StringToPermission = map[string]Permission{
- "full_access": FullAccess,
- }
-
- PermissionIDs = map[Permission]int{
- FullAccess: 1,
- }
-
- IdToPermission = map[int]Permission{
- 1: FullAccess,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/anthropic/permissions.yaml b/pkg/analyzer/analyzers/anthropic/permissions.yaml
deleted file mode 100644
index 8efa67e1905c..000000000000
--- a/pkg/analyzer/analyzers/anthropic/permissions.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-permissions:
- - full_access
diff --git a/pkg/analyzer/analyzers/anthropic/requests.go b/pkg/analyzer/analyzers/anthropic/requests.go
deleted file mode 100644
index 077f23d11450..000000000000
--- a/pkg/analyzer/analyzers/anthropic/requests.go
+++ /dev/null
@@ -1,341 +0,0 @@
-package anthropic
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-)
-
-var endpoints = map[string]string{
- // api key endpoints
- "models": "https://api.anthropic.com/v1/models",
- "messageBatches": "https://api.anthropic.com/v1/messages/batches",
-
- // admin key endpoints
- "orgUsers": "https://api.anthropic.com/v1/organizations/users",
- "workspaces": "https://api.anthropic.com/v1/organizations/workspaces",
- "workspaceMembers": "https://api.anthropic.com/v1/organizations/workspaces/%s/members", // require workspace id
- "apiKeys": "https://api.anthropic.com/v1/organizations/api_keys",
-}
-
-type ModelsResponse struct {
- Data []struct {
- ID string `json:"id"`
- DisplayName string `json:"display_name"`
- Type string `json:"type"`
- } `json:"data"`
-}
-
-type MessageResponse struct {
- Data []struct {
- ID string `json:"id"`
- Type string `json:"type"`
- ProcessingStatus string `json:"processing_status"`
- ExpiresAt string `json:"expires_at"`
- ResultsURL string `json:"results_url"`
- } `json:"data"`
-}
-
-type OrgUsersResponse struct {
- Data []struct {
- ID string `json:"id"`
- Type string `json:"type"`
- Email string `json:"email"`
- Name string `json:"name"`
- Role string `json:"role"`
- } `json:"data"`
-}
-
-type WorkspacesResponse struct {
- Data []struct {
- ID string `json:"id"`
- Type string `json:"type"`
- Name string `json:"name"`
- } `json:"data"`
-}
-
-type WorkspaceMembersResponse struct {
- Data []struct {
- WorkspaceID string `json:"workspace_id"`
- UserID string `json:"user_id"`
- Type string `json:"type"`
- WorkspaceRole string `json:"workspace_role"`
- } `json:"data"`
-}
-
-type APIKeysResponse struct {
- Data []struct {
- ID string `json:"id"`
- Type string `json:"type"`
- Name string `json:"name"`
- WorkspaceID string `json:"workspace_id"`
- CreatedBy struct {
- ID string `json:"id"`
- } `json:"created_by"`
- PartialKeyHint string `json:"partial_key_hint"`
- Status string `json:"status"`
- } `json:"data"`
-}
-
-// makeAnthropicRequest send the API request to passed url with passed key as API Key and return response body and status code
-func makeAnthropicRequest(client *http.Client, url, key string) ([]byte, int, error) {
- // create request
- req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
- if err != nil {
- return nil, 0, err
- }
-
- // add required keys in the header
- req.Header.Set("x-api-key", key)
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("anthropic-version", "2023-06-01")
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, 0, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- responseBodyByte, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, 0, err
- }
-
- return responseBodyByte, resp.StatusCode, nil
-}
-
-// captureAPIKeyResources capture resources associated with api key
-func captureAPIKeyResources(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
- if err := captureModels(client, apiKey, secretInfo); err != nil {
- return err
- }
-
- if err := captureMessageBatches(client, apiKey, secretInfo); err != nil {
- return err
- }
-
- return nil
-}
-
-// captureAdminKeyResources capture resources associated with admin key
-func captureAdminKeyResources(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
- if err := captureOrgUsers(client, adminKey, secretInfo); err != nil {
- return err
- }
-
- if err := captureWorkspaces(client, adminKey, secretInfo); err != nil {
- return err
- }
-
- if err := captureAPIKeys(client, adminKey, secretInfo); err != nil {
- return err
- }
-
- return nil
-}
-
-func captureModels(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeAnthropicRequest(client, endpoints["models"], apiKey)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var models ModelsResponse
-
- if err := json.Unmarshal(response, &models); err != nil {
- return err
- }
-
- for _, model := range models.Data {
- secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
- ID: model.ID,
- Name: model.DisplayName,
- Type: model.Type,
- })
- }
-
- return nil
- case http.StatusNotFound, http.StatusUnauthorized:
- return fmt.Errorf("invalid/revoked api-key")
- default:
- return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
- }
-}
-
-func captureMessageBatches(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeAnthropicRequest(client, endpoints["messageBatches"], apiKey)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var messageBatches MessageResponse
-
- if err := json.Unmarshal(response, &messageBatches); err != nil {
- return err
- }
-
- for _, messageBatch := range messageBatches.Data {
- secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
- ID: messageBatch.ID,
- Name: "", // no name
- Type: messageBatch.Type,
- Metadata: map[string]string{
- "expires_at": messageBatch.ExpiresAt,
- "results_url": messageBatch.ResultsURL,
- },
- })
- }
-
- return nil
- case http.StatusNotFound, http.StatusUnauthorized:
- return fmt.Errorf("invalid/revoked api-key")
- default:
- return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
- }
-}
-
-func captureOrgUsers(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeAnthropicRequest(client, endpoints["orgUsers"], adminKey)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var users OrgUsersResponse
-
- if err := json.Unmarshal(response, &users); err != nil {
- return err
- }
-
- for _, user := range users.Data {
- secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
- ID: user.ID,
- Name: user.Name,
- Type: user.Type,
- Metadata: map[string]string{
- "Role": user.Role,
- "Email": user.Email,
- },
- })
- }
-
- return nil
- case http.StatusNotFound, http.StatusUnauthorized:
- return fmt.Errorf("invalid/revoked api-key")
- default:
- return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
- }
-}
-
-func captureWorkspaces(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeAnthropicRequest(client, endpoints["workspaces"], adminKey)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var workspaces WorkspacesResponse
-
- if err := json.Unmarshal(response, &workspaces); err != nil {
- return err
- }
-
- for _, workspace := range workspaces.Data {
- resource := AnthropicResource{
- ID: workspace.ID,
- Name: workspace.Name,
- Type: workspace.Type,
- }
-
- secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, resource)
- // capture each workspace members
- if err := captureWorkspaceMembers(client, adminKey, resource, secretInfo); err != nil {
- return err
- }
- }
-
- return nil
- case http.StatusNotFound, http.StatusUnauthorized:
- return fmt.Errorf("invalid/revoked api-key")
- default:
- return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
- }
-}
-
-func captureWorkspaceMembers(client *http.Client, key string, parentWorkspace AnthropicResource, secretInfo *SecretInfo) error {
- response, statusCode, err := makeAnthropicRequest(client, fmt.Sprintf(endpoints["workspaceMembers"], parentWorkspace.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var members WorkspaceMembersResponse
-
- if err := json.Unmarshal(response, &members); err != nil {
- return err
- }
-
- for _, member := range members.Data {
- secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
- ID: fmt.Sprintf("anthropic/workspace/%s/member/%s", member.WorkspaceID, member.UserID),
- Name: member.UserID,
- Type: member.Type,
- Parent: &parentWorkspace,
- })
- }
-
- return nil
- case http.StatusNotFound, http.StatusUnauthorized:
- return fmt.Errorf("invalid/revoked api-key")
- default:
- return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
- }
-}
-
-func captureAPIKeys(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeAnthropicRequest(client, endpoints["apiKeys"], adminKey)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var apiKeys APIKeysResponse
-
- if err := json.Unmarshal(response, &apiKeys); err != nil {
- return err
- }
-
- for _, apiKey := range apiKeys.Data {
- secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
- ID: apiKey.ID,
- Name: apiKey.Name,
- Type: apiKey.Type,
- Metadata: map[string]string{
- "WorkspaceID": apiKey.WorkspaceID,
- "CreatedBy": apiKey.CreatedBy.ID,
- "PartialKeyHint": apiKey.PartialKeyHint,
- "Status": apiKey.Status,
- },
- })
- }
-
- return nil
- case http.StatusNotFound, http.StatusUnauthorized:
- return fmt.Errorf("invalid/revoked api-key")
- default:
- return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
- }
-}
diff --git a/pkg/analyzer/analyzers/anthropic/result_output.json b/pkg/analyzer/analyzers/anthropic/result_output.json
deleted file mode 100644
index 53f2c24c0209..000000000000
--- a/pkg/analyzer/analyzers/anthropic/result_output.json
+++ /dev/null
@@ -1,90 +0,0 @@
-{
- "AnalyzerType": 2,
- "Bindings": [
- {
- "Resource": {
- "Name": "Claude 3.5 Sonnet (New)",
- "FullyQualifiedName": "claude-3-5-sonnet-20241022",
- "Type": "model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Claude 3.5 Haiku",
- "FullyQualifiedName": "claude-3-5-haiku-20241022",
- "Type": "model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Claude 3.5 Sonnet (Old)",
- "FullyQualifiedName": "claude-3-5-sonnet-20240620",
- "Type": "model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Claude 3 Haiku",
- "FullyQualifiedName": "claude-3-haiku-20240307",
- "Type": "model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Claude 3 Opus",
- "FullyQualifiedName": "claude-3-opus-20240229",
- "Type": "model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "",
- "FullyQualifiedName": "msgbatch_015FDqbx29LDeVvbwwyCe314",
- "Type": "message_batch",
- "Metadata": {
- "expires_at": "2025-02-05T07:36:34.761695+00:00",
- "results_url": "https://api.anthropic.com/v1/messages/batches/msgbatch_015FDqbx29LDeVvbwwyCe314/results"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {
- "Valid_Key": true
- }
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/asana/asana.go b/pkg/analyzer/analyzers/asana/asana.go
deleted file mode 100644
index 81ed2ca125d9..000000000000
--- a/pkg/analyzer/analyzers/asana/asana.go
+++ /dev/null
@@ -1,160 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go asana
-package asana
-
-// ToDo: Add OAuth token support.
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeAsana }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("key not found in credInfo")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{}
-
- // resources/permission setup
- permissions := allPermissions()
- userResource := analyzers.Resource{
- Name: info.Data.Name,
- FullyQualifiedName: info.Data.ID,
- Type: "user",
- Metadata: map[string]any{
- "email": info.Data.Email,
- "type": info.Data.Type,
- },
- }
-
- // bindings to all permissions to resources
- bindings := analyzers.BindAllPermissions(userResource, permissions...)
- result.Bindings = append(result.Bindings, bindings...)
-
- // unbounded resources
- result.UnboundedResources = make([]analyzers.Resource, 0, len(info.Data.Workspaces))
- for _, workspace := range info.Data.Workspaces {
- resource := analyzers.Resource{
- Name: workspace.Name,
- FullyQualifiedName: workspace.ID,
- Type: "workspace",
- }
- result.UnboundedResources = append(result.UnboundedResources, resource)
- }
-
- return &result
-}
-
-type SecretInfo struct {
- Data struct {
- ID string `json:"gid"`
- Email string `json:"email"`
- Name string `json:"name"`
- Type string `json:"resource_type"`
- Workspaces []struct {
- ID string `json:"gid"`
- Name string `json:"name"`
- } `json:"workspaces"`
- } `json:"data"`
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- me, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] %s", err.Error())
- return
- }
- printMetadata(me)
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- var me SecretInfo
-
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", "https://app.asana.com/api/1.0/users/me", nil)
- if err != nil {
- return nil, err
- }
-
- req.Header.Set("Authorization", "Bearer "+key)
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
- }
-
- if resp.StatusCode != 200 {
- return nil, fmt.Errorf("Invalid Asana API Key")
- }
-
- defer resp.Body.Close()
-
- err = json.NewDecoder(resp.Body).Decode(&me)
- if err != nil {
- return nil, err
- }
-
- if me.Data.Email == "" {
- return nil, fmt.Errorf("Invalid Asana API Key")
- }
- return &me, nil
-}
-
-func printMetadata(me *SecretInfo) {
- color.Green("[!] Valid Asana API Key\n\n")
- color.Yellow("[i] User Information")
- color.Yellow(" Name: %s", me.Data.Name)
- color.Yellow(" Email: %s", me.Data.Email)
- color.Yellow(" Type: %s\n\n", me.Data.Type)
-
- color.Green("[i] Permissions: Full Access\n\n")
-
- color.Yellow("[i] Accessible Workspaces")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Workspace Name"})
- for _, workspace := range me.Data.Workspaces {
- t.AppendRow(table.Row{color.GreenString(workspace.Name)})
- }
- t.Render()
-}
-
-func allPermissions() []analyzers.Permission {
- permissions := make([]analyzers.Permission, 0, len(PermissionStrings))
- for _, permission := range PermissionStrings {
- permissions = append(permissions, analyzers.Permission{
- Value: permission,
- })
- }
- return permissions
-}
diff --git a/pkg/analyzer/analyzers/asana/asana_test.go b/pkg/analyzer/analyzers/asana/asana_test.go
deleted file mode 100644
index e71f4066af5f..000000000000
--- a/pkg/analyzer/analyzers/asana/asana_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package asana
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid Asana OAUTH Token",
- key: testSecrets.MustGetField("ASANAOAUTH_TOKEN"),
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/asana/expected_output.json b/pkg/analyzer/analyzers/asana/expected_output.json
deleted file mode 100644
index 598ef72e96d7..000000000000
--- a/pkg/analyzer/analyzers/asana/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":0,"Bindings":[{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"autdit_logs:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"portfolios:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"sections:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"tasks:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"user_task_lists:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"user_task_lists:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"autdit_logs:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"jobs:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"portfolios:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"project_memberships:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"project_memberships:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"users:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"users:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"memberships:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"custom_fields:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"goals:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"jobs:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"tags:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"teams:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"teams:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"custom_field_settings:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"projects:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"sections:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"allocations:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"custom_fields:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"projects:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"allocations:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"custom_field_settings:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"batch_api:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"events:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"tasks:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"rules:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"batch_api:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"goals:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"tags:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"memberships:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"attachments:read","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"attachments:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"events:write","Parent":null}},{"Resource":{"Name":"rendyplayground","FullyQualifiedName":"1200552284974896","Type":"user","Metadata":{"email":"rendyplayground@gmail.com","type":"user"},"Parent":null},"Permission":{"Value":"rules:write","Parent":null}}],"UnboundedResources":[{"Name":"Design","FullyQualifiedName":"1200552201649567","Type":"workspace","Metadata":null,"Parent":null}],"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/asana/permissions.go b/pkg/analyzer/analyzers/asana/permissions.go
deleted file mode 100644
index 577508c5df25..000000000000
--- a/pkg/analyzer/analyzers/asana/permissions.go
+++ /dev/null
@@ -1,256 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package asana
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- AllocationsRead Permission = iota
- AllocationsWrite Permission = iota
- AttachmentsRead Permission = iota
- AttachmentsWrite Permission = iota
- AutditLogsRead Permission = iota
- AutditLogsWrite Permission = iota
- CustomFieldsRead Permission = iota
- CustomFieldsWrite Permission = iota
- CustomFieldSettingsRead Permission = iota
- CustomFieldSettingsWrite Permission = iota
- BatchApiRead Permission = iota
- BatchApiWrite Permission = iota
- EventsRead Permission = iota
- EventsWrite Permission = iota
- GoalsRead Permission = iota
- GoalsWrite Permission = iota
- JobsRead Permission = iota
- JobsWrite Permission = iota
- PortfoliosRead Permission = iota
- PortfoliosWrite Permission = iota
- ProjectsRead Permission = iota
- ProjectsWrite Permission = iota
- ProjectMembershipsRead Permission = iota
- ProjectMembershipsWrite Permission = iota
- SectionsRead Permission = iota
- SectionsWrite Permission = iota
- TagsRead Permission = iota
- TagsWrite Permission = iota
- TasksRead Permission = iota
- TasksWrite Permission = iota
- TeamsRead Permission = iota
- TeamsWrite Permission = iota
- UsersRead Permission = iota
- UsersWrite Permission = iota
- UserTaskListsRead Permission = iota
- UserTaskListsWrite Permission = iota
- MembershipsRead Permission = iota
- MembershipsWrite Permission = iota
- RulesRead Permission = iota
- RulesWrite Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- AllocationsRead: "allocations:read",
- AllocationsWrite: "allocations:write",
- AttachmentsRead: "attachments:read",
- AttachmentsWrite: "attachments:write",
- AutditLogsRead: "autdit_logs:read",
- AutditLogsWrite: "autdit_logs:write",
- CustomFieldsRead: "custom_fields:read",
- CustomFieldsWrite: "custom_fields:write",
- CustomFieldSettingsRead: "custom_field_settings:read",
- CustomFieldSettingsWrite: "custom_field_settings:write",
- BatchApiRead: "batch_api:read",
- BatchApiWrite: "batch_api:write",
- EventsRead: "events:read",
- EventsWrite: "events:write",
- GoalsRead: "goals:read",
- GoalsWrite: "goals:write",
- JobsRead: "jobs:read",
- JobsWrite: "jobs:write",
- PortfoliosRead: "portfolios:read",
- PortfoliosWrite: "portfolios:write",
- ProjectsRead: "projects:read",
- ProjectsWrite: "projects:write",
- ProjectMembershipsRead: "project_memberships:read",
- ProjectMembershipsWrite: "project_memberships:write",
- SectionsRead: "sections:read",
- SectionsWrite: "sections:write",
- TagsRead: "tags:read",
- TagsWrite: "tags:write",
- TasksRead: "tasks:read",
- TasksWrite: "tasks:write",
- TeamsRead: "teams:read",
- TeamsWrite: "teams:write",
- UsersRead: "users:read",
- UsersWrite: "users:write",
- UserTaskListsRead: "user_task_lists:read",
- UserTaskListsWrite: "user_task_lists:write",
- MembershipsRead: "memberships:read",
- MembershipsWrite: "memberships:write",
- RulesRead: "rules:read",
- RulesWrite: "rules:write",
- }
-
- StringToPermission = map[string]Permission{
- "allocations:read": AllocationsRead,
- "allocations:write": AllocationsWrite,
- "attachments:read": AttachmentsRead,
- "attachments:write": AttachmentsWrite,
- "autdit_logs:read": AutditLogsRead,
- "autdit_logs:write": AutditLogsWrite,
- "custom_fields:read": CustomFieldsRead,
- "custom_fields:write": CustomFieldsWrite,
- "custom_field_settings:read": CustomFieldSettingsRead,
- "custom_field_settings:write": CustomFieldSettingsWrite,
- "batch_api:read": BatchApiRead,
- "batch_api:write": BatchApiWrite,
- "events:read": EventsRead,
- "events:write": EventsWrite,
- "goals:read": GoalsRead,
- "goals:write": GoalsWrite,
- "jobs:read": JobsRead,
- "jobs:write": JobsWrite,
- "portfolios:read": PortfoliosRead,
- "portfolios:write": PortfoliosWrite,
- "projects:read": ProjectsRead,
- "projects:write": ProjectsWrite,
- "project_memberships:read": ProjectMembershipsRead,
- "project_memberships:write": ProjectMembershipsWrite,
- "sections:read": SectionsRead,
- "sections:write": SectionsWrite,
- "tags:read": TagsRead,
- "tags:write": TagsWrite,
- "tasks:read": TasksRead,
- "tasks:write": TasksWrite,
- "teams:read": TeamsRead,
- "teams:write": TeamsWrite,
- "users:read": UsersRead,
- "users:write": UsersWrite,
- "user_task_lists:read": UserTaskListsRead,
- "user_task_lists:write": UserTaskListsWrite,
- "memberships:read": MembershipsRead,
- "memberships:write": MembershipsWrite,
- "rules:read": RulesRead,
- "rules:write": RulesWrite,
- }
-
- PermissionIDs = map[Permission]int{
- AllocationsRead: 1,
- AllocationsWrite: 2,
- AttachmentsRead: 3,
- AttachmentsWrite: 4,
- AutditLogsRead: 5,
- AutditLogsWrite: 6,
- CustomFieldsRead: 7,
- CustomFieldsWrite: 8,
- CustomFieldSettingsRead: 9,
- CustomFieldSettingsWrite: 10,
- BatchApiRead: 11,
- BatchApiWrite: 12,
- EventsRead: 13,
- EventsWrite: 14,
- GoalsRead: 15,
- GoalsWrite: 16,
- JobsRead: 17,
- JobsWrite: 18,
- PortfoliosRead: 19,
- PortfoliosWrite: 20,
- ProjectsRead: 21,
- ProjectsWrite: 22,
- ProjectMembershipsRead: 23,
- ProjectMembershipsWrite: 24,
- SectionsRead: 25,
- SectionsWrite: 26,
- TagsRead: 27,
- TagsWrite: 28,
- TasksRead: 29,
- TasksWrite: 30,
- TeamsRead: 31,
- TeamsWrite: 32,
- UsersRead: 33,
- UsersWrite: 34,
- UserTaskListsRead: 35,
- UserTaskListsWrite: 36,
- MembershipsRead: 37,
- MembershipsWrite: 38,
- RulesRead: 39,
- RulesWrite: 40,
- }
-
- IdToPermission = map[int]Permission{
- 1: AllocationsRead,
- 2: AllocationsWrite,
- 3: AttachmentsRead,
- 4: AttachmentsWrite,
- 5: AutditLogsRead,
- 6: AutditLogsWrite,
- 7: CustomFieldsRead,
- 8: CustomFieldsWrite,
- 9: CustomFieldSettingsRead,
- 10: CustomFieldSettingsWrite,
- 11: BatchApiRead,
- 12: BatchApiWrite,
- 13: EventsRead,
- 14: EventsWrite,
- 15: GoalsRead,
- 16: GoalsWrite,
- 17: JobsRead,
- 18: JobsWrite,
- 19: PortfoliosRead,
- 20: PortfoliosWrite,
- 21: ProjectsRead,
- 22: ProjectsWrite,
- 23: ProjectMembershipsRead,
- 24: ProjectMembershipsWrite,
- 25: SectionsRead,
- 26: SectionsWrite,
- 27: TagsRead,
- 28: TagsWrite,
- 29: TasksRead,
- 30: TasksWrite,
- 31: TeamsRead,
- 32: TeamsWrite,
- 33: UsersRead,
- 34: UsersWrite,
- 35: UserTaskListsRead,
- 36: UserTaskListsWrite,
- 37: MembershipsRead,
- 38: MembershipsWrite,
- 39: RulesRead,
- 40: RulesWrite,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/asana/permissions.yaml b/pkg/analyzer/analyzers/asana/permissions.yaml
deleted file mode 100644
index 3d3ebd2cbcf8..000000000000
--- a/pkg/analyzer/analyzers/asana/permissions.yaml
+++ /dev/null
@@ -1,41 +0,0 @@
-permissions:
- - allocations:read
- - allocations:write
- - attachments:read
- - attachments:write
- - autdit_logs:read
- - autdit_logs:write
- - custom_fields:read
- - custom_fields:write
- - custom_field_settings:read
- - custom_field_settings:write
- - batch_api:read
- - batch_api:write
- - events:read
- - events:write
- - goals:read
- - goals:write
- - jobs:read
- - jobs:write
- - portfolios:read
- - portfolios:write
- - projects:read
- - projects:write
- - project_memberships:read
- - project_memberships:write
- - sections:read
- - sections:write
- - tags:read
- - tags:write
- - tasks:read
- - tasks:write
- - teams:read
- - teams:write
- - users:read
- - users:write
- - user_task_lists:read
- - user_task_lists:write
- - memberships:read
- - memberships:write
- - rules:read
- - rules:write
diff --git a/pkg/analyzer/analyzers/bitbucket/bitbucket.go b/pkg/analyzer/analyzers/bitbucket/bitbucket.go
deleted file mode 100644
index fd9f89b98227..000000000000
--- a/pkg/analyzer/analyzers/bitbucket/bitbucket.go
+++ /dev/null
@@ -1,321 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go bitbucket
-package bitbucket
-
-import (
- "encoding/json"
- "errors"
- "net/http"
- "os"
- "sort"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-var resource_name_map = map[string]string{
- "repo_access_token": "Repository",
- "project_access_token": "Project",
- "workspace_access_token": "Workspace",
-}
-
-type SecretInfo struct {
- Type string
- OauthScopes []string
- Repos []Repo
-}
-
-type Repo struct {
- ID string `json:"uuid"`
- FullName string `json:"full_name"`
- RepoName string `json:"name"`
- Project struct {
- ID string `json:"uuid"`
- Name string `json:"name"`
- } `json:"project"`
- Workspace struct {
- ID string `json:"uuid"`
- Name string `json:"name"`
- } `json:"workspace"`
- IsPrivate bool `json:"is_private"`
- Owner struct {
- ID string `json:"uuid"`
- Username string `json:"username"`
- } `json:"owner"`
- Role string
-}
-
-type RepoJSON struct {
- Values []Repo `json:"values"`
-}
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeBitbucket }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("key not found in credentialInfo")
- }
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeBitbucket,
- }
-
- // add unbounded resources
- result.UnboundedResources = make([]analyzers.Resource, len(info.Repos))
- for i, repo := range info.Repos {
- result.UnboundedResources[i] = analyzers.Resource{
- Type: "repository",
- Name: repo.FullName,
- FullyQualifiedName: "bitbucket.com/repository/" + repo.ID,
- Parent: &analyzers.Resource{
- Type: "project",
- Name: repo.Project.Name,
- FullyQualifiedName: "bitbucket.com/project/" + repo.Project.ID,
- Parent: &analyzers.Resource{
- Type: "workspace",
- Name: repo.Workspace.Name,
- FullyQualifiedName: "bitbucket.com/workspace/" + repo.Workspace.ID,
- },
- },
- Metadata: map[string]any{
- "owner_id": repo.Owner.ID,
- "owner": repo.Owner.Username,
- "is_private": repo.IsPrivate,
- "role": repo.Role,
- },
- }
- }
-
- credentialResource := &analyzers.Resource{
- Type: info.Type,
- Name: resource_name_map[info.Type],
- FullyQualifiedName: "bitbucket.com/credential/" + info.Type,
- Metadata: map[string]any{
- "type": credential_type_map[info.Type],
- },
- }
-
- for _, scope := range info.OauthScopes {
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: *credentialResource,
- Permission: analyzers.Permission{
- Value: scope,
- },
- })
- }
-
- return &result
-}
-
-func getScopesAndType(cfg *config.Config, key string) (string, []string, error) {
- // client
- client := analyzers.NewAnalyzeClient(cfg)
-
- // request
- req, err := http.NewRequest("GET", "https://api.bitbucket.org/2.0/repositories", nil)
- if err != nil {
- return "", nil, err
- }
-
- // headers
- req.Header.Set("Authorization", "Bearer "+key)
-
- // response
- resp, err := client.Do(req)
- if err != nil {
- return "", nil, err
- }
- defer resp.Body.Close()
-
- // parse response headers
- credentialType := resp.Header.Get("x-credential-type")
- oauthScopes := resp.Header.Get("x-oauth-scopes")
-
- scopes := strings.Split(oauthScopes, ", ")
- return credentialType, scopes, nil
-}
-
-func scopesToBitbucketScopes(scopes ...analyzers.Permission) []BitbucketScope {
- scopesSlice := []BitbucketScope{}
- for _, scope := range scopes {
- scope := scope.Value
- mapping := oauth_scope_map[scope]
- for _, impliedScope := range mapping.ImpliedScopes {
- scopesSlice = append(scopesSlice, oauth_scope_map[impliedScope])
- }
- scopesSlice = append(scopesSlice, oauth_scope_map[scope])
- }
-
- // sort scopes by category
- sort.Sort(ByCategoryAndName(scopesSlice))
- return scopesSlice
-}
-
-func getRepositories(cfg *config.Config, key string, role string) (RepoJSON, error) {
- var repos RepoJSON
-
- // client
- client := analyzers.NewAnalyzeClient(cfg)
-
- // request
- req, err := http.NewRequest("GET", "https://api.bitbucket.org/2.0/repositories", nil)
- if err != nil {
- return repos, err
- }
-
- // headers
- req.Header.Set("Authorization", "Bearer "+key)
-
- // add query params
- q := req.URL.Query()
- q.Add("role", role)
- q.Add("pagelen", "100")
- req.URL.RawQuery = q.Encode()
-
- // response
- resp, err := client.Do(req)
- if err != nil {
- return repos, err
- }
- defer resp.Body.Close()
-
- // parse response body
- err = json.NewDecoder(resp.Body).Decode(&repos)
- if err != nil {
- return repos, err
- }
-
- return repos, nil
-}
-
-func getAllRepos(cfg *config.Config, key string) ([]Repo, error) {
- roles := []string{"member", "contributor", "admin", "owner"}
-
- var allRepos = make(map[string]Repo, 0)
- for _, role := range roles {
- repos, err := getRepositories(cfg, key, role)
- if err != nil {
- return nil, err
- }
- // purposefully overwriting, so that get the most permissive role
- for _, repo := range repos.Values {
- repo.Role = role
- allRepos[repo.FullName] = repo
- }
- }
- repoSlice := make([]Repo, 0, len(allRepos))
- for _, repo := range allRepos {
- repoSlice = append(repoSlice, repo)
- }
- return repoSlice, nil
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- credentialType, oauthScopes, err := getScopesAndType(cfg, key)
- if err != nil {
- return nil, err
- }
-
- // get all repos available to user
- // ToDo: pagination
- repos, err := getAllRepos(cfg, key)
- if err != nil {
- return nil, err
- }
- return &SecretInfo{
- Type: credentialType,
- OauthScopes: oauthScopes,
- Repos: repos,
- }, nil
-}
-
-func convertScopeToAnalyzerPermissions(scopes []string) []analyzers.Permission {
- permissions := make([]analyzers.Permission, 0, len(scopes))
- for _, scope := range scopes {
- permissions = append(permissions, analyzers.Permission{Value: scope})
- }
- return permissions
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
- printScopes(info.Type, convertScopeToAnalyzerPermissions(info.OauthScopes))
- printAccessibleRepositories(info.Repos)
-}
-
-func printScopes(credentialType string, scopes []analyzers.Permission) {
- if credentialType == "" {
- color.Red("[x] Invalid Bitbucket access token.")
- return
- }
- color.Green("[!] Valid Bitbucket access token.\n\n")
- color.Green("[i] Credential Type: %s\n\n", credential_type_map[credentialType])
-
- color.Yellow("[i] Access Token Scopes:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Category", "Permission"})
-
- currentCategory := ""
- for _, scope := range scopesToBitbucketScopes(scopes...) {
- if currentCategory != scope.Category {
- currentCategory = scope.Category
- t.AppendRow([]any{scope.Category, ""})
- }
- t.AppendRow([]any{"", color.GreenString(scope.Name)})
- }
-
- t.Render()
-
-}
-
-func printAccessibleRepositories(repos []Repo) {
- color.Yellow("\n[i] Accessible Repositories:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Repository", "Project", "Workspace", "Owner", "Is Private", "This User's Role"})
-
- for _, repo := range repos {
- private := ""
- if repo.IsPrivate {
- private = color.GreenString("Yes")
- } else {
- private = color.RedString("No")
- }
- t.AppendRow([]any{
- color.GreenString(repo.RepoName),
- color.GreenString(repo.Project.Name),
- color.GreenString(repo.Workspace.Name),
- color.GreenString(repo.Owner.Username),
- private,
- color.GreenString(repo.Role),
- })
- }
-
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/bitbucket/bitbucket_test.go b/pkg/analyzer/analyzers/bitbucket/bitbucket_test.go
deleted file mode 100644
index 240dff74f670..000000000000
--- a/pkg/analyzer/analyzers/bitbucket/bitbucket_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package bitbucket
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- sid string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid Bitbucket key",
- key: testSecrets.MustGetField("BITBUCKET_ANALYZE_TOKEN"),
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key, "sid": tt.sid})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = \n%s", gotIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/bitbucket/expected_output.json b/pkg/analyzer/analyzers/bitbucket/expected_output.json
deleted file mode 100644
index ea67d1fb8994..000000000000
--- a/pkg/analyzer/analyzers/bitbucket/expected_output.json
+++ /dev/null
@@ -1,92 +0,0 @@
-{
- "AnalyzerType": 3,
- "Bindings": [
- {
- "Resource": {
- "Name": "Repository",
- "FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
- "Type": "repo_access_token",
- "Metadata": {
- "type": "Repository Access Token (Can access 1 repository)"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "pipeline",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Repository",
- "FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
- "Type": "repo_access_token",
- "Metadata": {
- "type": "Repository Access Token (Can access 1 repository)"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "pullrequest",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Repository",
- "FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
- "Type": "repo_access_token",
- "Metadata": {
- "type": "Repository Access Token (Can access 1 repository)"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "runner",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Repository",
- "FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
- "Type": "repo_access_token",
- "Metadata": {
- "type": "Repository Access Token (Can access 1 repository)"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "webhook",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": [
- {
- "Name": "basit-trufflesec/repo1",
- "FullyQualifiedName": "bitbucket.com/repository/{8961ef70-000c-47ca-9348-5f9ecee875d6}",
- "Type": "repository",
- "Metadata": {
- "is_private": true,
- "owner": "basit-trufflesec",
- "owner_id": "{521b49b6-7709-484a-8aa8-ecc3a6da08eb}",
- "role": "admin"
- },
- "Parent": {
- "Name": "repo-analyzer",
- "FullyQualifiedName": "bitbucket.com/project/{8a693e10-087f-41fc-ba67-2d1414ab1c86}",
- "Type": "project",
- "Metadata": null,
- "Parent": {
- "Name": "basit-trufflesec",
- "FullyQualifiedName": "bitbucket.com/workspace/{521b49b6-7709-484a-8aa8-ecc3a6da08eb}",
- "Type": "workspace",
- "Metadata": null,
- "Parent": null
- }
- }
- }
- ],
- "Metadata": null
- }
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/bitbucket/permissions.go b/pkg/analyzer/analyzers/bitbucket/permissions.go
deleted file mode 100644
index 5dfe8e3cf402..000000000000
--- a/pkg/analyzer/analyzers/bitbucket/permissions.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package bitbucket
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Project Permission = iota
- ProjectAdmin Permission = iota
- Repository Permission = iota
- RepositoryWrite Permission = iota
- RepositoryAdmin Permission = iota
- RepositoryDelete Permission = iota
- Pullrequest Permission = iota
- PullrequestWrite Permission = iota
- Webhook Permission = iota
- Account Permission = iota
- Pipeline Permission = iota
- PipelineWrite Permission = iota
- PipelineVariable Permission = iota
- Runner Permission = iota
- RunnerWrite Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Project: "project",
- ProjectAdmin: "project:admin",
- Repository: "repository",
- RepositoryWrite: "repository:write",
- RepositoryAdmin: "repository:admin",
- RepositoryDelete: "repository:delete",
- Pullrequest: "pullrequest",
- PullrequestWrite: "pullrequest:write",
- Webhook: "webhook",
- Account: "account",
- Pipeline: "pipeline",
- PipelineWrite: "pipeline:write",
- PipelineVariable: "pipeline:variable",
- Runner: "runner",
- RunnerWrite: "runner:write",
- }
-
- StringToPermission = map[string]Permission{
- "project": Project,
- "project:admin": ProjectAdmin,
- "repository": Repository,
- "repository:write": RepositoryWrite,
- "repository:admin": RepositoryAdmin,
- "repository:delete": RepositoryDelete,
- "pullrequest": Pullrequest,
- "pullrequest:write": PullrequestWrite,
- "webhook": Webhook,
- "account": Account,
- "pipeline": Pipeline,
- "pipeline:write": PipelineWrite,
- "pipeline:variable": PipelineVariable,
- "runner": Runner,
- "runner:write": RunnerWrite,
- }
-
- PermissionIDs = map[Permission]int{
- Project: 1,
- ProjectAdmin: 2,
- Repository: 3,
- RepositoryWrite: 4,
- RepositoryAdmin: 5,
- RepositoryDelete: 6,
- Pullrequest: 7,
- PullrequestWrite: 8,
- Webhook: 9,
- Account: 10,
- Pipeline: 11,
- PipelineWrite: 12,
- PipelineVariable: 13,
- Runner: 14,
- RunnerWrite: 15,
- }
-
- IdToPermission = map[int]Permission{
- 1: Project,
- 2: ProjectAdmin,
- 3: Repository,
- 4: RepositoryWrite,
- 5: RepositoryAdmin,
- 6: RepositoryDelete,
- 7: Pullrequest,
- 8: PullrequestWrite,
- 9: Webhook,
- 10: Account,
- 11: Pipeline,
- 12: PipelineWrite,
- 13: PipelineVariable,
- 14: Runner,
- 15: RunnerWrite,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/bitbucket/permissions.yaml b/pkg/analyzer/analyzers/bitbucket/permissions.yaml
deleted file mode 100644
index 8a2eccd542e1..000000000000
--- a/pkg/analyzer/analyzers/bitbucket/permissions.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-permissions:
-- project
-- project:admin
-- repository
-- repository:write
-- repository:admin
-- repository:delete
-- pullrequest
-- pullrequest:write
-- webhook
-- account
-- pipeline
-- pipeline:write
-- pipeline:variable
-- runner
-- runner:write
diff --git a/pkg/analyzer/analyzers/bitbucket/scopes.go b/pkg/analyzer/analyzers/bitbucket/scopes.go
deleted file mode 100644
index 2214a0ccf4cf..000000000000
--- a/pkg/analyzer/analyzers/bitbucket/scopes.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package bitbucket
-
-var credential_type_map = map[string]string{
- "repo_access_token": "Repository Access Token (Can access 1 repository)",
- "project_access_token": "Project Access Token (Can access all repos in 1 project)",
- "workspace_access_token": "Workspace Access Token (Can access all projects and repos in 1 workspace)",
-}
-
-type BitbucketScope struct {
- Name string `json:"name"`
- Category string `json:"category"`
- ImpliedScopes []string `json:"implied_scopes"`
-}
-
-type ByCategoryAndName []BitbucketScope
-
-func (a ByCategoryAndName) Len() int { return len(a) }
-func (a ByCategoryAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a ByCategoryAndName) Less(i, j int) bool {
- categoryOrder := map[string]int{
- "Account": 0,
- "Projects": 1,
- "Repositories": 2,
- "Pull Requests": 3,
- "Webhooks": 4,
- "Pipelines": 5,
- "Runners": 6,
- }
- nameOrder := map[string]int{
- "Read": 0,
- "Write": 1,
- "Admin": 2,
- "Delete": 3,
- "Edit variables": 4,
- "Read and write": 5,
- }
-
- if categoryOrder[a[i].Category] != categoryOrder[a[j].Category] {
- return categoryOrder[a[i].Category] < categoryOrder[a[j].Category]
- }
- return nameOrder[a[i].Name] < nameOrder[a[j].Name]
-}
-
-var oauth_scope_map = map[string]BitbucketScope{
- "repository": {
- Name: "Read",
- Category: "Repositories",
- },
- "repository:write": {
- Name: "Write",
- Category: "Repositories",
- ImpliedScopes: []string{"repository"},
- },
- "repository:admin": {
- Name: "Admin",
- Category: "Repositories",
- },
- "repository:delete": {
- Name: "Delete",
- Category: "Repositories",
- },
- "pullrequest": {
- Name: "Read",
- Category: "Pull Requests",
- ImpliedScopes: []string{"repository"},
- },
- "pullrequest:write": {
- Name: "Write",
- Category: "Pull Requests",
- ImpliedScopes: []string{"pullrequest", "repository", "repository:write"},
- },
- "webhook": {
- Name: "Read and write",
- Category: "Webhooks",
- },
- "pipeline": {
- Name: "Read",
- Category: "Pipelines",
- },
- "pipeline:write": {
- Name: "Write",
- Category: "Pipelines",
- ImpliedScopes: []string{"pipeline"},
- },
- "pipeline:variable": {
- Name: "Edit variables",
- Category: "Pipelines",
- ImpliedScopes: []string{"pipeline", "pipeline:write"},
- },
- "runner": {
- Name: "Read",
- Category: "Runners",
- },
- "runner:write": {
- Name: "Write",
- Category: "Runners",
- ImpliedScopes: []string{"runner"},
- },
- "project": {
- Name: "Read",
- Category: "Projects",
- ImpliedScopes: []string{"repository"},
- },
- "project:admin": {
- Name: "Admin",
- Category: "Projects",
- },
- "account": {
- Name: "Read",
- Category: "Account",
- },
-}
diff --git a/pkg/analyzer/analyzers/client.go b/pkg/analyzer/analyzers/client.go
deleted file mode 100644
index 1bd9818676c8..000000000000
--- a/pkg/analyzer/analyzers/client.go
+++ /dev/null
@@ -1,171 +0,0 @@
-package analyzers
-
-import (
- "fmt"
- "net/http"
- "os"
- "strings"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "golang.org/x/time/rate"
-)
-
-type AnalyzeClient struct {
- http.Client
- LoggingEnabled bool
- LogFile string
-}
-
-func CreateLogFileName(baseName string) string {
- // Get the current time
- currentTime := time.Now()
-
- // Format the time as "2024_06_30_07_15_30"
- timeString := currentTime.Format("2006_01_02_15_04_05")
-
- // Create the log file name
- logFileName := fmt.Sprintf("%s_%s.log", timeString, baseName)
- return logFileName
-}
-
-type ClientOption func(*http.Client)
-
-// This returns a client that is restricted and filters out unsafe requests returning a success status code.
-func NewAnalyzeClient(cfg *config.Config, opts ...func(*http.Client)) *http.Client {
- client := &http.Client{
- Transport: AnalyzerRoundTripper{parent: http.DefaultTransport},
- }
- if cfg != nil && cfg.LoggingEnabled {
- client = &http.Client{
- Transport: LoggingRoundTripper{
- parent: client.Transport,
- logFile: cfg.LogFile,
- },
- }
- }
- for _, opt := range opts {
- opt(client)
- }
- return client
-}
-
-// This returns a client that is unrestricted and does not filter out unsafe requests returning a success status code.
-func NewAnalyzeClientUnrestricted(cfg *config.Config, opts ...ClientOption) *http.Client {
- client := &http.Client{
- Transport: http.DefaultTransport,
- }
- if cfg != nil && cfg.LoggingEnabled {
- client = &http.Client{
- Transport: LoggingRoundTripper{
- parent: client.Transport,
- logFile: cfg.LogFile,
- },
- }
- }
- for _, opt := range opts {
- opt(client)
- }
- return client
-}
-
-type LoggingRoundTripper struct {
- parent http.RoundTripper
- // TODO: io.Writer
- logFile string
-}
-
-func (r LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
- startTime := time.Now()
-
- resp, parentErr := r.parent.RoundTrip(req)
- if resp == nil {
- return resp, parentErr
- }
-
- // TODO: JSON
- var logEntry string
- if parentErr != nil {
- logEntry = fmt.Sprintf("Date: %s, Method: %s, Path: %s, Status: %d, Error: %s\n",
- startTime.Format(time.RFC3339),
- req.Method,
- req.URL.Path,
- resp.StatusCode,
- parentErr.Error(),
- )
- } else {
- logEntry = fmt.Sprintf("Date: %s, Method: %s, Path: %s, Status: %d\n",
- startTime.Format(time.RFC3339),
- req.Method,
- req.URL.Path,
- resp.StatusCode,
- )
- }
-
- // Open log file in append mode.
- file, err := os.OpenFile(r.logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
- if err != nil {
- return resp, fmt.Errorf("failed to open log file: %w", err)
- }
- defer file.Close()
-
- // Write log entry to file.
- if _, err := file.WriteString(logEntry); err != nil {
- return resp, fmt.Errorf("failed to write log entry to file: %w", err)
- }
-
- return resp, parentErr
-}
-
-type AnalyzerRoundTripper struct {
- parent http.RoundTripper
-}
-
-func (r AnalyzerRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
- resp, err := r.parent.RoundTrip(req)
- if err != nil || IsMethodSafe(req.Method) {
- return resp, err
- }
- // Check that unsafe methods did NOT return a valid status code.
- if resp.StatusCode >= 200 && resp.StatusCode < 300 {
- return resp, fmt.Errorf("non-safe request returned success")
- }
- return resp, nil
-}
-
-// IsMethodSafe is a helper method to check whether the HTTP method is safe according to MDN Web Docs.
-// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods#safe_idempotent_and_cacheable_request_methods
-func IsMethodSafe(method string) bool {
- switch strings.ToUpper(method) {
- case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
- return true
- default:
- return false
- }
-}
-
-type RateLimitRoundTripper struct {
- parent http.RoundTripper
- limiter *rate.Limiter
-}
-
-func (rt RateLimitRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
- if rt.parent == nil {
- rt.parent = http.DefaultTransport
- }
- if rt.limiter != nil {
- if err := rt.limiter.Wait(req.Context()); err != nil {
- return nil, err
- }
- }
- return rt.parent.RoundTrip(req)
-}
-
-func WithRateLimiter(l *rate.Limiter) ClientOption {
- return func(c *http.Client) {
- c.Transport = RateLimitRoundTripper{
- parent: c.Transport,
- limiter: l,
- }
- }
-}
diff --git a/pkg/analyzer/analyzers/client_test.go b/pkg/analyzer/analyzers/client_test.go
deleted file mode 100644
index 95dd37f9ae79..000000000000
--- a/pkg/analyzer/analyzers/client_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package analyzers
-
-import (
- "net/http"
- "net/http/httptest"
- "testing"
-)
-
-func TestAnalyzerClientUnsafeSuccess(t *testing.T) {
- testCases := []struct {
- name string
- method string
- expectedStatus int
- expectedError bool
- }{
- {
- name: "Safe method (GET)",
- method: http.MethodGet,
- expectedStatus: http.StatusOK,
- expectedError: false,
- },
- {
- name: "Safe method (HEAD)",
- method: http.MethodHead,
- expectedStatus: http.StatusOK,
- expectedError: false,
- },
- {
- name: "Safe method (OPTIONS)",
- method: http.MethodOptions,
- expectedStatus: http.StatusOK,
- expectedError: false,
- },
- {
- name: "Safe method (TRACE)",
- method: http.MethodTrace,
- expectedStatus: http.StatusOK,
- expectedError: false,
- },
- {
- name: "Unsafe method (POST) with success status",
- method: http.MethodPost,
- expectedStatus: http.StatusOK,
- expectedError: true,
- },
- {
- name: "Unsafe method (PUT) with success status",
- method: http.MethodPut,
- expectedStatus: http.StatusOK,
- expectedError: true,
- },
- {
- name: "Unsafe method (DELETE) with success status",
- method: http.MethodDelete,
- expectedStatus: http.StatusOK,
- expectedError: true,
- },
- {
- name: "Unsafe method (POST) with error status",
- method: http.MethodPost,
- expectedStatus: http.StatusInternalServerError,
- expectedError: false,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- // Create a test server that returns the expected status code
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(tc.expectedStatus)
- }))
- defer server.Close()
-
- // Create a test request
- req, err := http.NewRequest(tc.method, server.URL, nil)
- if err != nil {
- t.Fatalf("Failed to create test request: %v", err)
- }
-
- // Create the AnalyzerRoundTripper with a test client
- client := NewAnalyzeClient(nil)
-
- // Perform the request
- resp, err := client.Do(req)
- if resp != nil {
- _ = resp.Body.Close()
- }
-
- // Check the error
- if err != nil && !tc.expectedError {
- t.Errorf("Unexpected error: %v", err)
- } else if err == nil && tc.expectedError {
- t.Errorf("Expected error, but got nil")
- }
-
- // Check the response status code
- if resp != nil && resp.StatusCode != tc.expectedStatus {
- t.Errorf("Expected status code: %d, but got: %d", tc.expectedStatus, resp.StatusCode)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/databricks/databricks.go b/pkg/analyzer/analyzers/databricks/databricks.go
deleted file mode 100644
index 9ec61d1196ab..000000000000
--- a/pkg/analyzer/analyzers/databricks/databricks.go
+++ /dev/null
@@ -1,180 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go databricks
-package databricks
-
-import (
- "fmt"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeDataBricks
-}
-
-func (a Analyzer) Analyze(ctx context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- token, exist := credInfo["token"]
- if !exist {
- return nil, fmt.Errorf("key not found in credential info")
- }
-
- domain, exist := credInfo["domain"]
- if !exist {
- return nil, fmt.Errorf("domain not found in credential info")
- }
-
- info, err := AnalyzePermissions(ctx, a.Cfg, domain, token)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, domain, token string) {
- ctx := context.Background()
-
- info, err := AnalyzePermissions(ctx, cfg, domain, token)
- if err != nil {
- // just print the error in cli and continue as a partial success
- color.Red("[x] Error : %s", err.Error())
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- color.Green("[!] Valid DataBricks Access Token\n\n")
-
- printUserInfo(info.UserInfo)
- printTokenInfo(info.Tokens)
- printPermissions(info.TokenPermissionLevels)
-
- if len(info.Resources) > 0 {
- printResources(info.Resources)
- }
-
- color.Yellow("\n[i] Expires: %s", "N/A (Refer to Token Information Table)")
-}
-
-func AnalyzePermissions(ctx context.Context, cfg *config.Config, domain, token string) (*SecretInfo, error) {
- client := analyzers.NewAnalyzeClient(cfg)
-
- var secretInfo = &SecretInfo{}
-
- if err := captureUserInfo(ctx, client, domain, token, secretInfo); err != nil {
- return nil, err
- }
-
- if err := captureTokensInfo(ctx, client, domain, token, secretInfo); err != nil {
- return secretInfo, err
- }
-
- if err := captureTokenPermissions(ctx, client, domain, token, secretInfo); err != nil {
- return secretInfo, err
- }
-
- // capture resources
- if err := captureDataBricksResources(ctx, client, domain, token, secretInfo); err != nil {
- return secretInfo, err
- }
-
- return secretInfo, nil
-}
-
-// secretInfoToAnalyzerResult translate secret info to Analyzer Result
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeDataBricks,
- Metadata: map[string]any{},
- Bindings: make([]analyzers.Binding, 0),
- }
-
- // extract information from resource to create bindings and append to result bindings
- for _, resource := range info.Resources {
- binding := analyzers.Binding{
- Resource: analyzers.Resource{
- Name: resource.Name,
- FullyQualifiedName: fmt.Sprintf("databricks/%s/%s", resource.Type, resource.ID), // e.g: netlify/site/123
- Type: resource.Type,
- Metadata: map[string]any{}, // to avoid panic
- },
- }
-
- for key, value := range resource.Metadata {
- binding.Resource.Metadata[key] = value
- }
-
- // for each permission add a binding to resource
- for _, perm := range info.TokenPermissionLevels {
- binding.Permission = analyzers.Permission{
- Value: perm,
- }
-
- result.Bindings = append(result.Bindings, binding)
- }
- }
-
- return &result
-}
-
-// cli print functions
-func printUserInfo(user User) {
- color.Yellow("[i] User Information:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"ID", "UserName", "Primary Email"})
- t.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.UserName), color.GreenString(user.PrimaryEmail)})
-
- t.Render()
-}
-
-func printTokenInfo(tokens []Token) {
- color.Yellow("[i] Tokens Information:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Name", "Expiry Time", "Created By", "Last Used At"})
- for _, token := range tokens {
- t.AppendRow(table.Row{color.GreenString(token.Name),
- color.GreenString(token.ExpiryTime), color.GreenString(token.CreatedBy), color.GreenString(token.LastUsedDay)})
- }
- t.Render()
-}
-
-func printPermissions(permissions []string) {
- color.Yellow("[i] Token Permission Levels:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission Level"})
- for _, permission := range permissions {
- t.AppendRow(table.Row{color.GreenString(permission)})
- }
- t.Render()
-}
-
-func printResources(resources []DataBricksResource) {
- color.Yellow("[i] Resources:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Name", "Type"})
- for _, resource := range resources {
- t.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/databricks/databricks_test.go b/pkg/analyzer/analyzers/databricks/databricks_test.go
deleted file mode 100644
index b24afd9befcb..000000000000
--- a/pkg/analyzer/analyzers/databricks/databricks_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package databricks
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed result_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "analyzers1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- token := testSecrets.MustGetField("DATABRICKS_TOKEN")
- domain := testSecrets.MustGetField("DATABRICKS_DOMAIN")
-
- tests := []struct {
- name string
- domain string
- token string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid databricks credentials",
- domain: domain,
- token: token,
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"token": tt.token, "domain": tt.domain})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/databricks/models.go b/pkg/analyzer/analyzers/databricks/models.go
deleted file mode 100644
index a7be95bd8df4..000000000000
--- a/pkg/analyzer/analyzers/databricks/models.go
+++ /dev/null
@@ -1,125 +0,0 @@
-package databricks
-
-type ResourceType string
-
-func (r ResourceType) String() string {
- return string(r)
-}
-
-const (
- CurrentUser ResourceType = "User"
- TokensInfo ResourceType = "Token"
- TokenPermissions ResourceType = "Token Permission"
- Repositories ResourceType = "Repository"
- GitCredentials ResourceType = "Git Credential"
- Jobs ResourceType = "Job"
- Clusters ResourceType = "Cluster"
- Groups ResourceType = "Group"
- Users ResourceType = "Member"
-)
-
-type SecretInfo struct {
- UserInfo User
- TokenPermissionLevels []string
- Tokens []Token
- Resources []DataBricksResource
-}
-
-type User struct {
- ID string
- UserName string
- PrimaryEmail string
-}
-
-type Token struct {
- ID string
- Name string
- ExpiryTime string
- CreatedBy string
- LastUsedDay string
-}
-
-type DataBricksResource struct {
- ID string
- Name string
- Type string
- Metadata map[string]string
-}
-
-// API response models
-
-type CurrentUserInfo struct {
- ID string `json:"id"`
- UserName string `json:"userName"`
- Emails []struct {
- Display string `json:"display"`
- Value string `json:"value"`
- Primary bool `json:"primary"`
- } `json:"emails"`
-}
-
-type Tokens struct {
- TokensInfo []struct {
- ID string `json:"token_id"`
- Name string `json:"comment"`
- ExpiryTime int `json:"expiry_time"`
- LastUsedDay int `json:"last_used_day"`
- CreatedBy string `json:"created_by_username"`
- } `json:"token_infos"`
-}
-
-type Permissions struct {
- PermissionLevels []struct {
- Description string `json:"description"`
- PermissionLevel string `json:"permission_level"`
- } `json:"permission_levels"`
-}
-
-type ReposResponse struct {
- Repositories []struct {
- ID string `json:"id"`
- Path string `json:"path"`
- Provider string `json:"provider"`
- URL string `json:"url"`
- } `json:"repos"`
-}
-
-type GitCreds struct {
- Credentials []struct {
- ID string `json:"credentials_id"`
- UserName string `json:"git_username"`
- Provider string `json:"git_provider"`
- } `json:"credentials"`
-}
-
-type JobsResponse struct {
- Jobs []struct {
- ID string `json:"job_id"`
- Name string `json:"name"`
- Description string `json:"description"`
- } `json:"jobs"`
-}
-
-type ClustersResponse struct {
- Clusters []struct {
- ID string `json:"cluster_id"`
- Name string `json:"cluster_name"`
- CreatedBy string `json:"creator_user_name"`
- } `json:"clusters"`
-}
-
-type GroupsResponse struct {
- Resources []struct {
- ID string `json:"id"`
- Name string `json:"displayName"`
- // TODO: capture members if needed
- } `json:"Resources"`
-}
-
-type UsersResponse struct {
- Resources []struct {
- ID string `json:"id"`
- UserName string `json:"userName"`
- Active bool `json:"active"`
- } `json:"Resources"`
-}
diff --git a/pkg/analyzer/analyzers/databricks/permissions.go b/pkg/analyzer/analyzers/databricks/permissions.go
deleted file mode 100644
index fe7bcff57397..000000000000
--- a/pkg/analyzer/analyzers/databricks/permissions.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package databricks
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- CanManage Permission = iota
- CanUse Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- CanManage: "CAN_MANAGE",
- CanUse: "CAN_USE",
- }
-
- StringToPermission = map[string]Permission{
- "CAN_MANAGE": CanManage,
- "CAN_USE": CanUse,
- }
-
- PermissionIDs = map[Permission]int{
- CanManage: 1,
- CanUse: 2,
- }
-
- IdToPermission = map[int]Permission{
- 1: CanManage,
- 2: CanUse,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/databricks/permissions.yaml b/pkg/analyzer/analyzers/databricks/permissions.yaml
deleted file mode 100644
index 7b75a67a1c9a..000000000000
--- a/pkg/analyzer/analyzers/databricks/permissions.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-permissions:
- - CAN_MANAGE
- - CAN_USE
diff --git a/pkg/analyzer/analyzers/databricks/requests.go b/pkg/analyzer/analyzers/databricks/requests.go
deleted file mode 100644
index 823db099ecc7..000000000000
--- a/pkg/analyzer/analyzers/databricks/requests.go
+++ /dev/null
@@ -1,298 +0,0 @@
-package databricks
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var (
- // ErrUnauthorized is returned when the Databricks API answers with HTTP-401.
- errUnAuthorized = errors.New("invalid/expired personal access token")
-
- apiEndpoints = map[ResourceType]string{
- CurrentUser: "/api/2.0/preview/scim/v2/Me",
- TokensInfo: "/api/2.0/token-management/tokens",
- TokenPermissions: "/api/2.0/permissions/authorization/tokens/permissionLevels",
- Repositories: "/api/2.0/repos",
- GitCredentials: "/api/2.0/git-credentials",
- Jobs: "/api/2.2/jobs/list",
- Clusters: "/api/2.1/clusters/list",
- Groups: "/api/2.0/preview/scim/v2/Groups",
- Users: "/api/2.0/preview/scim/v2/Users",
- /*
- TODO:
- - https://docs.databricks.com/api/gcp/workspace/workspace/list (list content inside path)
- - http://docs.databricks.com/api/gcp/workspace/libraries/allclusterlibrarystatuses (list cluster statuses)
- */
- }
-)
-
-// doAndDecode performs an authenticated GET request against the constructed
-// Databricks URL and JSON-decodes the response into the supplied result.
-//
-// The generic type parameter T allows the caller to decide which concrete
-// struct the response should be unmarshalled into:
-func doAndDecode[T any](ctx context.Context, client *http.Client, domain string, rt ResourceType, token string, out *T) error {
- u := url.URL{
- Scheme: "https",
- Host: domain,
- Path: apiEndpoints[rt],
- }
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody)
- if err != nil {
- return fmt.Errorf("building request: %w", err)
- }
-
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- req.Header.Set("Accept", "application/json")
-
- // Execute request and read / decode body. We stream directly into the
- // decoder instead of loading the whole response into memory first.
- resp, err := client.Do(req)
- if err != nil {
- return fmt.Errorf("performing request: %w", err)
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
- return fmt.Errorf("decoding response: %w", err)
- }
-
- return nil
- case http.StatusUnauthorized:
- return errUnAuthorized
- default:
- return fmt.Errorf("unexpected status code %d for API %s", resp.StatusCode, apiEndpoints[rt])
- }
-}
-
-func captureDataBricksResources(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {
- if err := captureRepos(ctx, client, domain, token, secretInfo); err != nil {
- return err
- }
-
- if err := captureGitCreds(ctx, client, domain, token, secretInfo); err != nil {
- return err
- }
-
- if err := captureJobs(ctx, client, domain, token, secretInfo); err != nil {
- return err
- }
-
- if err := captureClusters(ctx, client, domain, token, secretInfo); err != nil {
- return err
- }
-
- if err := captureGroups(ctx, client, domain, token, secretInfo); err != nil {
- return err
- }
-
- if err := captureUsers(ctx, client, domain, token, secretInfo); err != nil {
- return err
- }
-
- return nil
-}
-
-func captureUserInfo(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {
- var user CurrentUserInfo
-
- if err := doAndDecode(ctx, client, domain, CurrentUser, token, &user); err != nil {
- return err
- }
-
- secretInfo.UserInfo = User{
- ID: user.ID,
- UserName: user.UserName,
- }
-
- for _, email := range user.Emails {
- if email.Primary {
- secretInfo.UserInfo.PrimaryEmail = email.Value
- }
- }
-
- return nil
-}
-
-func captureTokensInfo(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {
- var tokens Tokens
-
- if err := doAndDecode(ctx, client, domain, TokensInfo, token, &tokens); err != nil {
- return err
- }
-
- for _, t := range tokens.TokensInfo {
- secretInfo.Tokens = append(secretInfo.Tokens, Token{
- ID: t.ID,
- Name: t.Name,
- ExpiryTime: readableTime(t.ExpiryTime),
- LastUsedDay: readableTime(t.LastUsedDay),
- CreatedBy: t.CreatedBy,
- })
- }
-
- return nil
-}
-
-func captureTokenPermissions(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {
- var permissions Permissions
-
- if err := doAndDecode(ctx, client, domain, TokenPermissions, token, &permissions); err != nil {
- return err
- }
-
- for _, item := range permissions.PermissionLevels {
- secretInfo.TokenPermissionLevels = append(secretInfo.TokenPermissionLevels, item.PermissionLevel)
- }
-
- return nil
-}
-
-func captureRepos(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {
- var repos ReposResponse
-
- if err := doAndDecode(ctx, client, domain, Repositories, token, &repos); err != nil {
- return err
- }
-
- for _, repo := range repos.Repositories {
- if repo.ID == "" {
- repo.ID = repo.URL
- }
-
- secretInfo.Resources = append(secretInfo.Resources, DataBricksResource{
- ID: repo.ID,
- Name: repo.Path,
- Type: Repositories.String(),
- Metadata: map[string]string{
- "provider": repo.Provider,
- "url": repo.URL,
- },
- })
- }
-
- return nil
-}
-
-func captureGitCreds(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {
- var creds GitCreds
-
- if err := doAndDecode(ctx, client, domain, GitCredentials, token, &creds); err != nil {
- return err
- }
-
- for _, credential := range creds.Credentials {
- secretInfo.Resources = append(secretInfo.Resources, DataBricksResource{
- ID: credential.ID,
- Name: credential.UserName,
- Type: GitCredentials.String(),
- Metadata: map[string]string{
- "provider": credential.Provider,
- },
- })
- }
-
- return nil
-}
-
-func captureJobs(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {
- var jobs JobsResponse
-
- if err := doAndDecode(ctx, client, domain, Jobs, token, &jobs); err != nil {
- return err
- }
-
- for _, job := range jobs.Jobs {
- secretInfo.Resources = append(secretInfo.Resources, DataBricksResource{
- ID: job.ID,
- Name: job.Name,
- Type: Jobs.String(),
- Metadata: map[string]string{
- "description": job.Description,
- },
- })
- }
-
- return nil
-}
-
-func captureClusters(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {
- var clusters ClustersResponse
-
- if err := doAndDecode(ctx, client, domain, Clusters, token, &clusters); err != nil {
- return err
- }
-
- for _, cluster := range clusters.Clusters {
- secretInfo.Resources = append(secretInfo.Resources, DataBricksResource{
- ID: cluster.ID,
- Name: cluster.Name,
- Type: Clusters.String(),
- Metadata: map[string]string{
- "created by": cluster.CreatedBy,
- },
- })
- }
-
- return nil
-}
-
-func captureGroups(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {
- var groups GroupsResponse
-
- if err := doAndDecode(ctx, client, domain, Groups, token, &groups); err != nil {
- return err
- }
-
- for _, group := range groups.Resources {
- secretInfo.Resources = append(secretInfo.Resources, DataBricksResource{
- ID: group.ID,
- Name: group.Name,
- Type: Groups.String(),
- })
- }
-
- return nil
-}
-
-func captureUsers(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {
- var users UsersResponse
-
- if err := doAndDecode(ctx, client, domain, Users, token, &users); err != nil {
- return err
- }
-
- for _, user := range users.Resources {
- secretInfo.Resources = append(secretInfo.Resources, DataBricksResource{
- ID: user.ID,
- Name: user.UserName,
- Type: Users.String(),
- Metadata: map[string]string{
- "active": fmt.Sprintf("%t", user.Active),
- },
- })
- }
-
- return nil
-}
-
-func readableTime(timestamp int) string {
- timestampMillis := int64(timestamp)
- t := time.Unix(timestampMillis/1000, (timestampMillis%1000)*int64(time.Millisecond))
-
- return t.Format("2006-01-02 15:04:05")
-}
diff --git a/pkg/analyzer/analyzers/databricks/result_output.json b/pkg/analyzer/analyzers/databricks/result_output.json
deleted file mode 100644
index 4858fd8f4beb..000000000000
--- a/pkg/analyzer/analyzers/databricks/result_output.json
+++ /dev/null
@@ -1,119 +0,0 @@
-{
- "AnalyzerType": 41,
- "Bindings": [
- {
- "Resource": {
- "Name": "admins",
- "FullyQualifiedName": "databricks/Group/601448505198850",
- "Type": "Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "CAN_MANAGE",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "admins",
- "FullyQualifiedName": "databricks/Group/601448505198850",
- "Type": "Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "CAN_USE",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "kashif.khan@trufflesec.com",
- "FullyQualifiedName": "databricks/Member/8639341364955455",
- "Type": "Member",
- "Metadata": {
- "active": "true"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "CAN_MANAGE",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "kashif.khan@trufflesec.com",
- "FullyQualifiedName": "databricks/Member/8639341364955455",
- "Type": "Member",
- "Metadata": {
- "active": "true"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "CAN_USE",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "kashifkhan",
- "FullyQualifiedName": "databricks/Git Credential/",
- "Type": "Git Credential",
- "Metadata": {
- "provider": "gitHub"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "CAN_MANAGE",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "kashifkhan",
- "FullyQualifiedName": "databricks/Git Credential/",
- "Type": "Git Credential",
- "Metadata": {
- "provider": "gitHub"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "CAN_USE",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "users",
- "FullyQualifiedName": "databricks/Group/1000729253926373",
- "Type": "Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "CAN_MANAGE",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "users",
- "FullyQualifiedName": "databricks/Group/1000729253926373",
- "Type": "Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "CAN_USE",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {}
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/datadog/datadog.go b/pkg/analyzer/analyzers/datadog/datadog.go
deleted file mode 100644
index a83a63fc4a97..000000000000
--- a/pkg/analyzer/analyzers/datadog/datadog.go
+++ /dev/null
@@ -1,222 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go datadog
-package datadog
-
-import (
- "errors"
- "fmt"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeDatadog
-}
-
-// Analyze performs the analysis of the Datadog API key and returns the analyzer result.
-func (a Analyzer) Analyze(ctx context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- apiKey, exist := credInfo["apiKey"]
- if !exist {
- return nil, errors.New("API key not found in credentials info")
- }
-
- // Get appKey if provided
- appKey := credInfo["appKey"]
-
- info, err := AnalyzePermissions(a.Cfg, apiKey, appKey)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, apiKey string, appKey string) {
- info, err := AnalyzePermissions(cfg, apiKey, appKey)
- if err != nil {
- // just print the error in cli and continue as a partial success
- color.Red("[x] Error : %s", err.Error())
- }
-
- color.Green("[i] Valid Datadog API Key\n")
-
- printUser(info.User)
- printResources(info.Resources)
- printPermissions(info.Permissions)
-}
-
-// AnalyzePermissions will collect all the scopes assigned to token along with resource it can access
-func AnalyzePermissions(cfg *config.Config, apiKey string, appKey string) (*SecretInfo, error) {
- // create the http client
- client := analyzers.NewAnalyzeClient(cfg)
-
- var secretInfo = &SecretInfo{}
-
- // First detect which DataDog domain works with this API key
- baseURL, err := DetectDomain(client, apiKey, appKey)
- if err != nil {
- return nil, fmt.Errorf("[x] %v", err)
- }
-
- // capture user information in secretInfo
- // If the application key is scoped, user information cannot be retrieved even if all the permissions are granted
- // This is a non-documented Endpoint and can lead to unexpected behavior in future updates
- // If user information is not retrieved, we will move ahead with the rest of the analysis and print the error
- _ = CaptureUserInformation(client, baseURL, apiKey, appKey, secretInfo)
-
- // capture resources in secretInfo
- if err := CaptureResources(client, baseURL, apiKey, appKey, secretInfo); err != nil {
- return nil, fmt.Errorf("failed to fetch resources: %v", err)
- }
-
- // capture permissions in secretInfo
- if err := CapturePermissions(client, baseURL, apiKey, appKey, secretInfo); err != nil {
- return nil, fmt.Errorf("failed to fetch permissions: %v", err)
- }
-
- return secretInfo, nil
-}
-
-// secretInfoToAnalyzerResult translate secret info to Analyzer Result
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeDatadog,
- Metadata: map[string]any{},
- Bindings: make([]analyzers.Binding, 0),
- }
-
- // Create user resource to use as parent
- var userResource *analyzers.Resource
- if info.User.Id != "" {
- userResource = &analyzers.Resource{
- FullyQualifiedName: info.User.Id,
- Name: info.User.Name,
- Type: "User",
- Metadata: map[string]any{
- "email": info.User.Email,
- },
- }
- }
-
- permissionBindings := secretInfoPermissionsToAnalyzerPermission(info.Permissions)
- result.Bindings = analyzers.BindAllPermissions(*userResource, *permissionBindings...)
-
- // Extract information from resources to create bindings
- for _, resource := range info.Resources {
- resource := secretInfoResourceToAnalyzerResource(resource)
-
- // Set the user resource as parent if available
- if userResource != nil {
- resource.Parent = userResource
- }
-
- binding := analyzers.Binding{
- Resource: *resource,
- }
-
- result.Bindings = append(result.Bindings, binding)
- }
-
- return &result
-}
-
-// secretInfoPermissionsToAnalyzerPermission translate secret info Permission to analyzer resource for binding
-func secretInfoPermissionsToAnalyzerPermission(perms []Permission) *[]analyzers.Permission {
- permissions := make([]analyzers.Permission, 0, len(perms))
- for _, perm := range perms {
- permissions = append(permissions, analyzers.Permission{
- Value: perm.Title,
- })
- }
- return &permissions
-}
-
-// secretInfoResourceToAnalyzerResource translate secret info Resource to analyzer resource for binding
-func secretInfoResourceToAnalyzerResource(resource Resource) *analyzers.Resource {
- analyzerRes := analyzers.Resource{
- FullyQualifiedName: resource.ID,
- Name: resource.Name,
- Type: resource.Type,
- Metadata: map[string]any{},
- }
-
- for key, value := range resource.MetaData {
- analyzerRes.Metadata[key] = value
- }
-
- return &analyzerRes
-}
-
-func printUser(user User) {
- if user.Id == "" {
- color.Red("\n[x] User information not available")
- return
- }
-
- color.Green("\n[i] User Information:")
- userTable := table.NewWriter()
- userTable.SetOutputMirror(os.Stdout)
- userTable.AppendHeader(table.Row{"User Id", "Name", "Email"})
- userTable.AppendRow(table.Row{color.GreenString(user.Id), color.GreenString(user.Name), color.GreenString(user.Email)})
- userTable.Render()
-}
-
-func printResources(resources []Resource) {
- if len(resources) == 0 {
- color.Red("[x] No resources found")
- return
- }
-
- color.Green("\n[i] Resources:")
- resourceTable := table.NewWriter()
- resourceTable.SetOutputMirror(os.Stdout)
- resourceTable.AppendHeader(table.Row{"Name", "Type"})
- for _, resource := range resources {
- resourceTable.AppendRow(table.Row{
- color.GreenString(resource.Name),
- color.GreenString(resource.Type),
- })
- }
- resourceTable.Render()
-}
-
-func printPermissions(permissions []Permission) {
- if len(permissions) == 0 {
- color.Red("[x] No permissions found")
- return
- }
-
- color.Green("\n[i] Permissions:")
- permissionTable := table.NewWriter()
- permissionTable.SetOutputMirror(os.Stdout)
- permissionTable.AppendHeader(table.Row{"Title", "Name", "Description"})
-
- // Set wrapping for long descriptions
- permissionTable.SetColumnConfigs([]table.ColumnConfig{
- {Number: 3, WidthMax: 50},
- })
-
- for _, permission := range permissions {
- permissionTable.AppendRow(table.Row{
- color.GreenString(permission.Title),
- color.GreenString(permission.Name),
- color.GreenString(permission.Description),
- })
- }
- permissionTable.Render()
-}
diff --git a/pkg/analyzer/analyzers/datadog/datadog_test.go b/pkg/analyzer/analyzers/datadog/datadog_test.go
deleted file mode 100644
index 46b089447046..000000000000
--- a/pkg/analyzer/analyzers/datadog/datadog_test.go
+++ /dev/null
@@ -1,142 +0,0 @@
-package datadog
-
-import (
- _ "embed"
- "encoding/json"
- "fmt"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*2)
- defer cancel()
-
- // Get API keys from GCP
- var apiKey, appKey string
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "analyzers1")
- if err != nil {
- t.Fatalf("Could not get test secrets from GCP: %s", err)
- }
-
- // Get the required credentials
- apiKey = testSecrets.MustGetField("DATADOG_API_KEY")
- appKey = testSecrets.MustGetField("DATADOG_APP_KEY")
-
- // Fail if credentials are not available
- if apiKey == "" || appKey == "" {
- t.Fatalf("Datadog credentials are required for this test")
- }
-
- tests := []struct {
- name string
- apiKey string
- appKey string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid datadog credentials",
- apiKey: apiKey,
- appKey: appKey,
- want: expectedOutput,
- wantErr: false,
- },
- {
- name: "invalid credentials",
- apiKey: "invalid_api_key",
- appKey: "invalid_app_key",
- want: nil,
- wantErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"apiKey": tt.apiKey, "appKey": tt.appKey})
-
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Skip verification for error cases
- if tt.wantErr {
- return
- }
-
- // For valid cases, verify we got a result
- if got == nil {
- t.Errorf("Analyzer.Analyze() = nil, want non-nil")
- return
- }
-
- // Verify type is correct
- if got.AnalyzerType != analyzers.AnalyzerTypeDatadog {
- t.Errorf("Analyzer.Analyze() returned wrong analyzer type, got %d want %d",
- got.AnalyzerType, analyzers.AnalyzerTypeDatadog)
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- fmt.Println(string(gotJSON))
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal(tt.want, &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/datadog/expected_output.json b/pkg/analyzer/analyzers/datadog/expected_output.json
deleted file mode 100644
index bea9ea9416af..000000000000
--- a/pkg/analyzer/analyzers/datadog/expected_output.json
+++ /dev/null
@@ -1,1045 +0,0 @@
-{
- "AnalyzerType": 37,
- "Bindings": [
- {
- "Resource": {
- "Name": "Dashboards Read",
- "FullyQualifiedName": "Dashboards Read",
- "Type": "Dashboards",
- "Metadata": { "Resource": "Dashboards" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "dashboards_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Dashboards Write",
- "FullyQualifiedName": "Dashboards Write",
- "Type": "Dashboards",
- "Metadata": { "Resource": "Dashboards" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "dashboards_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Dashboards Public Share",
- "FullyQualifiedName": "Dashboards Public Share",
- "Type": "Dashboards",
- "Metadata": { "Resource": "Dashboards" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "dashboards_public_share", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Monitors Read",
- "FullyQualifiedName": "Monitors Read",
- "Type": "Monitors",
- "Metadata": { "Resource": "Monitors" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "monitors_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Monitors Write",
- "FullyQualifiedName": "Monitors Write",
- "Type": "Monitors",
- "Metadata": { "Resource": "Monitors" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "monitors_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Logs Modify Indexes",
- "FullyQualifiedName": "Logs Modify Indexes",
- "Type": "Logs",
- "Metadata": { "Resource": "Logs" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "logs_modify_indexes", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Logs Write Pipelines",
- "FullyQualifiedName": "Logs Write Pipelines",
- "Type": "Logs",
- "Metadata": { "Resource": "Logs" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "logs_write_pipelines", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Logs Write Archives",
- "FullyQualifiedName": "Logs Write Archives",
- "Type": "Logs",
- "Metadata": { "Resource": "Logs" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "logs_write_archives", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Logs Generate Metrics",
- "FullyQualifiedName": "Logs Generate Metrics",
- "Type": "Logs",
- "Metadata": { "Resource": "Logs" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "logs_generate_metrics", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Manage Downtimes",
- "FullyQualifiedName": "Manage Downtimes",
- "Type": "Monitors",
- "Metadata": { "Resource": "Monitors" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "monitors_downtime", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Logs Read Data",
- "FullyQualifiedName": "Logs Read Data",
- "Type": "Logs",
- "Metadata": { "Resource": "Logs" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "logs_read_data", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Logs Read Archives",
- "FullyQualifiedName": "Logs Read Archives",
- "Type": "Logs",
- "Metadata": { "Resource": "Logs" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "logs_read_archives", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Security Rules Read",
- "FullyQualifiedName": "Security Rules Read",
- "Type": "Security Monitoring",
- "Metadata": { "Resource": "Security Monitoring" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "security_monitoring_rules_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Security Signals Read",
- "FullyQualifiedName": "Security Signals Read",
- "Type": "Security Monitoring",
- "Metadata": { "Resource": "Security Monitoring" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "security_monitoring_signals_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Security Signals Write",
- "FullyQualifiedName": "Security Signals Write",
- "Type": "Security Monitoring",
- "Metadata": { "Resource": "Security Monitoring" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "security_monitoring_signals_write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User Access Invite",
- "FullyQualifiedName": "User Access Invite",
- "Type": "Users",
- "Metadata": { "Resource": "Users" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "user_access_invite", "Parent": null }
- },
- {
- "Resource": {
- "Name": "User App Keys",
- "FullyQualifiedName": "User App Keys",
- "Type": "Key Management",
- "Metadata": { "Resource": "Key Management" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "user_app_keys", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Org App Keys Read",
- "FullyQualifiedName": "Org App Keys Read",
- "Type": "Key Management",
- "Metadata": { "Resource": "Key Management" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "org_app_keys_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Org App Keys Write",
- "FullyQualifiedName": "Org App Keys Write",
- "Type": "Key Management",
- "Metadata": { "Resource": "Key Management" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "org_app_keys_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "User Access Manage",
- "FullyQualifiedName": "User Access Manage",
- "Type": "Users",
- "Metadata": { "Resource": "Users" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "user_access_manage", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Synthetics Private Locations Read",
- "FullyQualifiedName": "Synthetics Private Locations Read",
- "Type": "Synthetics",
- "Metadata": { "Resource": "Synthetics" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "synthetics_private_location_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Synthetics Private Locations Write",
- "FullyQualifiedName": "Synthetics Private Locations Write",
- "Type": "Synthetics",
- "Metadata": { "Resource": "Synthetics" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "synthetics_private_location_write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Usage Read",
- "FullyQualifiedName": "Usage Read",
- "Type": "Usage Metering",
- "Metadata": { "Resource": "Usage Metering" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "usage_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Metric Tags Write",
- "FullyQualifiedName": "Metric Tags Write",
- "Type": "Metrics",
- "Metadata": { "Resource": "Metrics" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "metric_tags_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Audit Trail Read",
- "FullyQualifiedName": "Audit Trail Read",
- "Type": "Audit",
- "Metadata": { "Resource": "Audit" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "audit_logs_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "API Keys Read",
- "FullyQualifiedName": "API Keys Read",
- "Type": "Key Management",
- "Metadata": { "Resource": "Key Management" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "api_keys_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "API Keys Write",
- "FullyQualifiedName": "API Keys Write",
- "Type": "Key Management",
- "Metadata": { "Resource": "Key Management" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "api_keys_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Synthetics Global Variable Read",
- "FullyQualifiedName": "Synthetics Global Variable Read",
- "Type": "Synthetics",
- "Metadata": { "Resource": "Synthetics" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "synthetics_global_variable_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Synthetics Global Variable Write",
- "FullyQualifiedName": "Synthetics Global Variable Write",
- "Type": "Synthetics",
- "Metadata": { "Resource": "Synthetics" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "synthetics_global_variable_write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Synthetics Read",
- "FullyQualifiedName": "Synthetics Read",
- "Type": "Synthetics",
- "Metadata": { "Resource": "Synthetics" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "synthetics_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Synthetics Write",
- "FullyQualifiedName": "Synthetics Write",
- "Type": "Synthetics",
- "Metadata": { "Resource": "Synthetics" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "synthetics_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Synthetics Default Settings Read",
- "FullyQualifiedName": "Synthetics Default Settings Read",
- "Type": "Synthetics",
- "Metadata": { "Resource": "Synthetics" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "synthetics_default_settings_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Service Account Write",
- "FullyQualifiedName": "Service Account Write",
- "Type": "Service Accounts",
- "Metadata": { "Resource": "Service Accounts" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "service_account_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "APM Read",
- "FullyQualifiedName": "APM Read",
- "Type": "APM",
- "Metadata": { "Resource": "APM" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "apm_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "APM Retention Filters Read",
- "FullyQualifiedName": "APM Retention Filters Read",
- "Type": "APM",
- "Metadata": { "Resource": "APM" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "apm_retention_filter_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "APM Retention Filters Write",
- "FullyQualifiedName": "APM Retention Filters Write",
- "Type": "APM",
- "Metadata": { "Resource": "APM" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "apm_retention_filter_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "RUM Apps Write",
- "FullyQualifiedName": "RUM Apps Write",
- "Type": "RUM",
- "Metadata": { "Resource": "RUM" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "rum_apps_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Data Scanner Read",
- "FullyQualifiedName": "Data Scanner Read",
- "Type": "Sensitive Data Scanner",
- "Metadata": { "Resource": "Sensitive Data Scanner" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "data_scanner_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Data Scanner Write",
- "FullyQualifiedName": "Data Scanner Write",
- "Type": "Sensitive Data Scanner",
- "Metadata": { "Resource": "Sensitive Data Scanner" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "data_scanner_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Org Management",
- "FullyQualifiedName": "Org Management",
- "Type": "Organizations",
- "Metadata": { "Resource": "Organizations" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "org_management", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Security Filters Read",
- "FullyQualifiedName": "Security Filters Read",
- "Type": "Security Monitoring",
- "Metadata": { "Resource": "Security Monitoring" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "security_monitoring_filters_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Security Filters Write",
- "FullyQualifiedName": "Security Filters Write",
- "Type": "Security Monitoring",
- "Metadata": { "Resource": "Security Monitoring" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "security_monitoring_filters_write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Incidents Read",
- "FullyQualifiedName": "Incidents Read",
- "Type": "Incidents",
- "Metadata": { "Resource": "Incidents" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "incident_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Incidents Write",
- "FullyQualifiedName": "Incidents Write",
- "Type": "Incidents",
- "Metadata": { "Resource": "Incidents" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "incident_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Incident Settings Write",
- "FullyQualifiedName": "Incident Settings Write",
- "Type": "Incidents",
- "Metadata": { "Resource": "Incidents" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "incident_settings_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "RUM Apps Read",
- "FullyQualifiedName": "RUM Apps Read",
- "Type": "RUM",
- "Metadata": { "Resource": "RUM" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "rum_apps_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "APM Generate Metrics",
- "FullyQualifiedName": "APM Generate Metrics",
- "Type": "APM",
- "Metadata": { "Resource": "APM" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "apm_generate_metrics", "Parent": null }
- },
- {
- "Resource": {
- "Name": "APM Pipelines Write",
- "FullyQualifiedName": "APM Pipelines Write",
- "Type": "APM",
- "Metadata": { "Resource": "APM" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "apm_pipelines_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "APM Pipelines Read",
- "FullyQualifiedName": "APM Pipelines Read",
- "Type": "APM",
- "Metadata": { "Resource": "APM" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "apm_pipelines_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Workflows Read",
- "FullyQualifiedName": "Workflows Read",
- "Type": "Workflows",
- "Metadata": { "Resource": "Workflows" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "workflows_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Workflows Write",
- "FullyQualifiedName": "Workflows Write",
- "Type": "Workflows",
- "Metadata": { "Resource": "Workflows" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "workflows_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Connections Read",
- "FullyQualifiedName": "Connections Read",
- "Type": "Connections",
- "Metadata": { "Resource": "Connections" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "connections_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Connections Write",
- "FullyQualifiedName": "Connections Write",
- "Type": "Connections",
- "Metadata": { "Resource": "Connections" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "connections_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Notebooks Read",
- "FullyQualifiedName": "Notebooks Read",
- "Type": "Notebooks",
- "Metadata": { "Resource": "Notebooks" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "notebooks_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Notebooks Write",
- "FullyQualifiedName": "Notebooks Write",
- "Type": "Notebooks",
- "Metadata": { "Resource": "Notebooks" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "notebooks_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "AWS Configurations Manage",
- "FullyQualifiedName": "AWS Configurations Manage",
- "Type": "Integrations",
- "Metadata": { "Resource": "Integrations" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "aws_configurations_manage", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Azure Configurations Manage",
- "FullyQualifiedName": "Azure Configurations Manage",
- "Type": "Integrations",
- "Metadata": { "Resource": "Integrations" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "azure_configurations_manage", "Parent": null }
- },
- {
- "Resource": {
- "Name": "GCP Configurations Manage",
- "FullyQualifiedName": "GCP Configurations Manage",
- "Type": "Integrations",
- "Metadata": { "Resource": "Integrations" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "gcp_configurations_manage", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Integrations Manage",
- "FullyQualifiedName": "Integrations Manage",
- "Type": "Integrations",
- "Metadata": { "Resource": "Integrations" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "manage_integrations", "Parent": null }
- },
- {
- "Resource": {
- "Name": "SLOs Read",
- "FullyQualifiedName": "SLOs Read",
- "Type": "SLOs",
- "Metadata": { "Resource": "SLOs" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "slos_read", "Parent": null }
- },
- {
- "Resource": {
- "Name": "SLOs Write",
- "FullyQualifiedName": "SLOs Write",
- "Type": "SLOs",
- "Metadata": { "Resource": "SLOs" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "slos_write", "Parent": null }
- },
- {
- "Resource": {
- "Name": "SLOs Status Corrections",
- "FullyQualifiedName": "SLOs Status Corrections",
- "Type": "SLOs",
- "Metadata": { "Resource": "SLOs" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "slos_corrections", "Parent": null }
- },
- {
- "Resource": {
- "Name": "Monitor Configuration Policy Write",
- "FullyQualifiedName": "Monitor Configuration Policy Write",
- "Type": "Monitors",
- "Metadata": { "Resource": "Monitors" },
- "Parent": {
- "Name": "Truffle Sec",
- "FullyQualifiedName": "",
- "Type": "user",
- "Metadata": { "email": "detectors@trufflesec.com" },
- "Parent": null
- }
- },
- "Permission": { "Value": "monitor_config_policy_write", "Parent": null }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {}
-}
diff --git a/pkg/analyzer/analyzers/datadog/models.go b/pkg/analyzer/analyzers/datadog/models.go
deleted file mode 100644
index 5df15c919ffb..000000000000
--- a/pkg/analyzer/analyzers/datadog/models.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package datadog
-
-import "sync"
-
-// Resource type constants for consistent usage
-const (
- ResourceTypeValidate = "Validate"
- ResourceTypeCurrentUser = "Current User"
- ResourceTypeDashboard = "Dashboard"
- ResourceTypeMonitor = "Monitor"
-)
-
-// Permission represents a permission granted to an API key
-type Permission struct {
- Name string
- Title string
- Description string
- MetaData map[string]string
-}
-
-// SecretInfo holds all information gathered about a Datadog API key
-type SecretInfo struct {
- User User
- Permissions []Permission
-
- mu sync.RWMutex
- Resources []Resource
-}
-
-// User is the information about the user to whom the token belongs
-type User struct {
- Id string
- Name string
- Email string
-}
-
-// Resource represents a Datadog resource
-type Resource struct {
- ID string
- Name string
- Type string
- MetaData map[string]string
-}
-
-// API response structures
-type currentUserResponse struct {
- Data struct {
- Id string `json:"id"`
- Attributes struct {
- Name string `json:"name"`
- Email string `json:"email"`
- } `json:"attributes"`
- } `json:"data"`
-}
-
-type dashboardResponse struct {
- Dashboards []DashboardItem `json:"dashboards"`
-}
-
-type DashboardItem struct {
- ID string `json:"id"`
- Title string `json:"title"`
- URL string `json:"url"`
- IsReadOnly bool `json:"is_read_only"`
- CreatedAt string `json:"created_at"`
- ModifiedAt string `json:"modified_at"`
- AuthorHandle string `json:"author_handle"`
- Description *string `json:"description"`
- LayoutType string `json:"layout_type"`
- DeletedAt *string `json:"deleted_at"`
-}
-
-type monitorResponse []struct {
- ID int `json:"id"`
- Name string `json:"name"`
-}
-
-// appendResource adds a resource to secret info resources list
-func (s *SecretInfo) appendResource(resource Resource) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- s.Resources = append(s.Resources, resource)
-}
diff --git a/pkg/analyzer/analyzers/datadog/permissions.yaml b/pkg/analyzer/analyzers/datadog/permissions.yaml
deleted file mode 100644
index 4c83569f9d05..000000000000
--- a/pkg/analyzer/analyzers/datadog/permissions.yaml
+++ /dev/null
@@ -1,69 +0,0 @@
-permissions:
- - dashboards_read
- - dashboards_write
- - dashboards_public_share
- - monitors_read
- - monitors_write
- - logs_modify_indexes
- - logs_write_pipelines
- - logs_write_archives
- - logs_generate_metrics
- - monitors_downtime
- - logs_read_data
- - logs_read_archives
- - security_monitoring_rules_read
- - security_monitoring_rules_write
- - security_monitoring_signals_read
- - security_monitoring_signals_write
- - user_access_invite
- - user_app_keys
- - org_app_keys_read
- - org_app_keys_write
- - user_access_manage
- - synthetics_private_location_read
- - synthetics_private_location_write
- - usage_read
- - metric_tags_write
- - audit_logs_read
- - api_keys_read
- - api_keys_write
- - synthetics_global_variable_read
- - synthetics_global_variable_write
- - synthetics_read
- - synthetics_write
- - synthetics_default_settings_read
- - service_account_write
- - apm_read
- - apm_retention_filter_read
- - apm_retention_filter_write
- - rum_apps_write
- - data_scanner_read
- - data_scanner_write
- - org_management
- - security_monitoring_filters_read
- - security_monitoring_filters_write
- - incident_read
- - incident_write
- - incident_settings_write
- - rum_apps_read
- - security_monitoring_notification_profiles_read
- - security_monitoring_notification_profiles_write
- - apm_generate_metrics
- - apm_pipelines_write
- - apm_pipelines_read
- - observability_pipelines_read
- - workflows_read
- - workflows_write
- - workflows_run
- - connections_read
- - connections_write
- - notebooks_read
- - notebooks_write
- - aws_configurations_manage
- - azure_configurations_manage
- - gcp_configurations_manage
- - manage_integrations
- - slos_read
- - slos_write
- - slos_corrections
- - monitor_config_policy_write
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/datadog/requests.go b/pkg/analyzer/analyzers/datadog/requests.go
deleted file mode 100644
index 6904a2910d9e..000000000000
--- a/pkg/analyzer/analyzers/datadog/requests.go
+++ /dev/null
@@ -1,381 +0,0 @@
-package datadog
-
-import (
- "context"
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "slices"
- "strconv"
- "sync"
- "time"
-)
-
-// Constants and configuration
-const (
- defaultTimeout = 12 * time.Second
- apiKeyHeader = "DD-API-KEY"
- appKeyHeader = "DD-APPLICATION-KEY"
-)
-
-// List of all DataDog domains to try
-var datadogDomains = []string{
- "https://api.us5.datadoghq.com/api", // Default domain
- "https://api.app.datadoghq.com/api",
- "https://api.us3.datadoghq.com/api",
- "https://api.app.datadoghq.eu/api",
- "https://api.app.ddog-gov.com/api",
- "https://api.ap1.datadoghq.com/api",
-}
-
-// Endpoints map for API paths
-var endpoints = map[string]string{
- ResourceTypeCurrentUser: "/v2/current_user",
- ResourceTypeDashboard: "/v1/dashboard",
- ResourceTypeMonitor: "/v1/monitor",
- ResourceTypeValidate: "/v1/validate",
-}
-
-//go:embed scopes.json
-var scopesConfig []byte
-
-// --------------------------------
-// Data models
-// --------------------------------
-
-// HttpStatusTest defines a test for checking HTTP endpoint permissions
-type HttpStatusTest struct {
- Method string `json:"method"`
- Endpoint string `json:"endpoint"`
- ValidStatuses []int `json:"valid_statuses"`
- InvalidStatuses []int `json:"invalid_statuses"`
-}
-
-// Scope represents a permission scope with a test
-type Scope struct {
- Name string `json:"name"`
- Title string `json:"title"`
- Description string `json:"description"`
- Resource string `json:"resource"`
- HttpTest HttpStatusTest `json:"test"`
-}
-
-// --------------------------------
-// Domain detection
-// --------------------------------
-
-// DetectDomain tries each DataDog domain to find a working one
-func DetectDomain(client *http.Client, apiKey string, appKey string) (string, error) {
- for _, domain := range datadogDomains {
- // Use a simple endpoint to test if the domain works
- endpoint := domain + endpoints[ResourceTypeValidate]
-
- ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
- defer cancel()
-
- // Create request
- req, err := http.NewRequestWithContext(ctx, "GET", endpoint, http.NoBody)
- if err != nil {
- continue // Skip to next domain if request creation fails
- }
-
- // Add required keys in the header
- req.Header.Set(apiKeyHeader, apiKey)
-
- if appKey != "" {
- req.Header.Set(appKeyHeader, appKey)
- }
-
- resp, err := client.Do(req)
-
- if err != nil {
- continue // Skip to next domain if request fails
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- // If we get a response that's not a connection error, this domain works
- if resp.StatusCode == http.StatusOK {
- return domain, nil
- }
- }
-
- return "", errors.New("unable to validate any DataDog domain with the provided API key")
-}
-
-// --------------------------------
-// HTTP request utilities
-// --------------------------------
-
-// makeDataDogRequest sends an HTTP GET API request to the specified endpoint with auth tokens
-func makeDataDogRequest(client *http.Client, baseURL, endpoint, method, apiKey string, appKey string) ([]byte, int, error) {
- ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
- defer cancel()
-
- // create request
- req, err := http.NewRequestWithContext(ctx, method, baseURL+endpoint, http.NoBody)
- if err != nil {
- return nil, 0, err
- }
-
- // add required keys in the header
- req.Header.Set(apiKeyHeader, apiKey)
-
- if appKey != "" {
- req.Header.Set(appKeyHeader, appKey)
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, 0, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- responseBodyByte, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, 0, err
- }
-
- return responseBodyByte, resp.StatusCode, nil
-}
-
-// RunTest executes an HTTP test against an API endpoint with provided headers
-func (h *HttpStatusTest) RunTest(client *http.Client, baseURL string, headers map[string]string) (bool, error) {
- apiKey := headers[apiKeyHeader]
- appKey := headers[appKeyHeader]
-
- _, statusCode, err := makeDataDogRequest(client, baseURL, h.Endpoint, h.Method, apiKey, appKey)
-
- if err != nil {
- fmt.Printf("Error making request: %v\n", err)
- return false, err
- }
-
- // Check response status code
- switch {
- case slices.Contains(h.ValidStatuses, statusCode):
- return true, nil
- case slices.Contains(h.InvalidStatuses, statusCode):
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// --------------------------------
-// Data capture functions
-// --------------------------------
-
-// CaptureUserInformation retrieves and stores user information
-func CaptureUserInformation(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {
- caller, err := getCurrentUserInfo(client, baseURL, apiKey, appKey)
- if err != nil {
- return err
- }
-
- addUserToSecretInfo(caller, secretInfo)
-
- return nil
-}
-
-// CaptureResources retrieves and stores dashboard and monitor resources
-func CaptureResources(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {
- var wg sync.WaitGroup
- errChan := make(chan error, 2) // Buffer size matches the number of tasks
-
- // helper to launch tasks concurrently
- launchTask := func(task func() error) {
- wg.Add(1)
- go func() {
- defer wg.Done()
- if err := task(); err != nil {
- errChan <- err
- }
- }()
- }
-
- launchTask(func() error { return captureDashboard(client, baseURL, apiKey, appKey, secretInfo) })
- launchTask(func() error { return captureMonitor(client, baseURL, apiKey, appKey, secretInfo) })
-
- // Wait for all tasks to complete
- wg.Wait()
- close(errChan)
-
- // Collect any errors
- var errs []error
- for err := range errChan {
- errs = append(errs, err)
- }
-
- if len(errs) > 0 {
- return errors.Join(errs...)
- }
-
- return nil
-}
-
-// CapturePermissions tests and records available permissions
-func CapturePermissions(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {
- scopes, err := readInScopes()
- if err != nil {
- return fmt.Errorf("reading in scopes: %w", err)
- }
-
- permissions := make([]Permission, 0)
- headers := map[string]string{
- apiKeyHeader: apiKey,
- appKeyHeader: appKey,
- }
-
- for _, scope := range scopes {
- status, err := scope.HttpTest.RunTest(client, baseURL, headers)
- if err != nil {
- return fmt.Errorf("running test for scope %s: %w", scope.Name, err)
- }
-
- metadata := map[string]string{
- "Resource": scope.Resource,
- }
-
- if status {
- permission := Permission{
- Name: scope.Name,
- Title: scope.Title,
- Description: scope.Description,
- MetaData: metadata,
- }
- permissions = append(permissions, permission)
- }
- }
-
- secretInfo.Permissions = permissions
- return nil
-}
-
-// --------------------------------
-// Resource capture helper functions
-// --------------------------------
-
-// getCurrentUserInfo retrieves information about the current user
-func getCurrentUserInfo(client *http.Client, baseURL, apiKey, appKey string) (*currentUserResponse, error) {
- response, statusCode, err := makeDataDogRequest(client, baseURL, endpoints[ResourceTypeCurrentUser], http.MethodGet, apiKey, appKey)
- if err != nil {
- return nil, err
- }
-
- switch statusCode {
- case http.StatusOK:
- var caller = ¤tUserResponse{}
- if err := json.Unmarshal(response, caller); err != nil {
- return nil, fmt.Errorf("unmarshalling user response: %w", err)
- }
- return caller, nil
- case http.StatusUnauthorized:
- return nil, errors.New("invalid API key or application key")
- default:
- return nil, fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// addUserToSecretInfo adds user information to the secret info object
-func addUserToSecretInfo(caller *currentUserResponse, secretInfo *SecretInfo) {
- user := User{
- Id: caller.Data.Id,
- Name: caller.Data.Attributes.Name,
- Email: caller.Data.Attributes.Email,
- }
-
- secretInfo.User = user
-}
-
-// captureDashboard retrieves dashboard information
-func captureDashboard(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeDataDogRequest(client, baseURL, endpoints[ResourceTypeDashboard], http.MethodGet, apiKey, appKey)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var dashboardResponse = &dashboardResponse{}
- if err := json.Unmarshal(response, dashboardResponse); err != nil {
- return fmt.Errorf("unmarshalling dashboard response: %w", err)
- }
-
- for _, dashboard := range dashboardResponse.Dashboards {
- metadata := map[string]string{
- "Layout Type": dashboard.LayoutType,
- "URL": dashboard.URL,
- "Author Handle": dashboard.AuthorHandle,
- }
-
- resource := Resource{
- ID: dashboard.ID,
- Name: dashboard.Title,
- Type: ResourceTypeDashboard,
- MetaData: metadata,
- }
-
- secretInfo.appendResource(resource)
- }
- return nil
- case http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code for dashboard API: %d", statusCode)
- }
-}
-
-// captureMonitor retrieves monitor information
-func captureMonitor(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeDataDogRequest(client, baseURL, endpoints[ResourceTypeMonitor], http.MethodGet, apiKey, appKey)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var monitorResponse = &monitorResponse{}
- if err := json.Unmarshal(response, monitorResponse); err != nil {
- return fmt.Errorf("unmarshalling monitor response: %w", err)
- }
-
- for _, monitor := range *monitorResponse {
- resource := Resource{
- ID: strconv.Itoa(monitor.ID),
- Name: monitor.Name,
- Type: ResourceTypeMonitor,
- }
-
- secretInfo.appendResource(resource)
- }
- return nil
- case http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code for monitor API: %d", statusCode)
- }
-}
-
-// --------------------------------
-// Utility functions
-// --------------------------------
-
-// readInScopes loads permission scopes from the embedded configuration
-func readInScopes() ([]Scope, error) {
- var scopes []Scope
- if err := json.Unmarshal(scopesConfig, &scopes); err != nil {
- return nil, fmt.Errorf("unmarshalling scopes config: %w", err)
- }
- return scopes, nil
-}
diff --git a/pkg/analyzer/analyzers/datadog/scopes.json b/pkg/analyzer/analyzers/datadog/scopes.json
deleted file mode 100644
index 632606988a3d..000000000000
--- a/pkg/analyzer/analyzers/datadog/scopes.json
+++ /dev/null
@@ -1,818 +0,0 @@
-[
- {
- "name": "dashboards_read",
- "title": "Dashboards Read",
- "description": "View dashboards.",
- "resource": "Dashboards",
- "test": {
- "endpoint": "/v1/dashboard",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "dashboards_write",
- "title": "Dashboards Write",
- "description": "Create and change dashboards.",
- "resource": "Dashboards",
- "test": {
- "endpoint": "/v1/dashboard",
- "method": "POST",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "dashboards_public_share",
- "title": "Dashboards Public Share",
- "description": "Create, modify and delete shared dashboards with share type 'Public'. These dashboards can be accessed by anyone on the internet.",
- "resource": "Dashboards",
- "test": {
- "endpoint": "/v1/dashboard/public",
- "method": "POST",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "monitors_read",
- "title": "Monitors Read",
- "description": "View monitors.",
- "resource": "Monitors",
- "test": {
- "endpoint": "/v1/monitor",
- "method": "GET",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "monitors_write",
- "title": "Monitors Write",
- "description": "Edit and delete individual monitors.",
- "resource": "Monitors",
- "test": {
- "endpoint": "/v1/monitor",
- "method": "POST",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "logs_modify_indexes",
- "title": "Logs Modify Indexes",
- "description": "Read and modify all indexes in your account.",
- "resource": "Logs",
- "test": {
- "endpoint": "/v1/logs/config/indexes/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "logs_write_pipelines",
- "title": "Logs Write Pipelines",
- "description": "Add and change log pipeline configurations.",
- "resource": "Logs",
- "test": {
- "endpoint": "/v1/logs/config/pipelines/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "logs_write_archives",
- "title": "Logs Write Archives",
- "description": "Add and edit Log Archives.",
- "resource": "Logs",
- "test": {
- "endpoint": "/v2/logs/config/archives/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "logs_generate_metrics",
- "title": "Logs Generate Metrics",
- "description": "Create custom metrics from logs.",
- "resource": "Logs",
- "test": {
- "endpoint": "/v2/logs/config/metrics",
- "method": "POST",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "monitors_downtime",
- "title": "Manage Downtimes",
- "description": "Set downtimes to suppress alerts from any monitor in an organization.",
- "resource": "Monitors",
- "test": {
- "endpoint": "/v1/downtime",
- "method": "POST",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "logs_read_data",
- "title": "Logs Read Data",
- "description": "Read log data. In order to read log data, a user must have both this permission and Logs Read Index Data.",
- "resource": "Logs",
- "test": {
- "endpoint": "/v2/logs/events",
- "method": "GET",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "logs_read_archives",
- "title": "Logs Read Archives",
- "description": "Read Log Archives location and use it for rehydration.",
- "resource": "Logs",
- "test": {
- "endpoint": "/v2/logs/config/archives",
- "method": "GET",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "security_monitoring_rules_read",
- "title": "Security Rules Read",
- "description": "Read Detection Rules.",
- "resource": "Security Monitoring",
- "test": {
- "endpoint": "/v2/cloud_security_management/custom_frameworks/must/not-exist",
- "method": "GET",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "security_monitoring_rules_write",
- "title": "Security Rules Write",
- "description": "Create and edit Detection Rules.",
- "resource": "Security Monitoring",
- "test": {
- "endpoint": "/v2/security_monitoring/rules",
- "method": "POST",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "security_monitoring_signals_read",
- "title": "Security Signals Read",
- "description": "View Security Signals.",
- "resource": "Security Monitoring",
- "test": {
- "endpoint": "/v2/security_monitoring/signals",
- "method": "GET",
- "valid_statuses": [200, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "security_monitoring_signals_write",
- "title": "Security Signals Write",
- "description": "Modify Security Signals.",
- "resource": "Security Monitoring",
- "test": {
- "endpoint": "/v1/security_analytics/signals/must-not-exist/add_to_incident",
- "method": "PATCH",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "user_access_invite",
- "title": "User Access Invite",
- "description": "Invite other users to your organization.",
- "resource": "Users",
- "test": {
- "endpoint": "/v2/user_invitations/does-not-exist",
- "method": "GET",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "title": "User App Keys",
- "name": "user_app_keys",
- "description": "View and manage Application Keys owned by the user.",
- "resource": "Key Management",
- "test": {
- "endpoint": "/v2/current_user/application_keys/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "org_app_keys_read",
- "title": "Org App Keys Read",
- "description": "View Application Keys owned by all users in the organization.",
- "resource": "Key Management",
- "test": {
- "endpoint": "/v2/application_keys",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "org_app_keys_write",
- "title": "Org App Keys Write",
- "description": "Manage Application Keys owned by all users in the organization.",
- "resource": "Key Management",
- "test": {
- "endpoint": "/v2/application_keys/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "user_access_manage",
- "title": "User Access Manage",
- "description": "Disable users, manage user roles, manage SAML-to-role mappings, and configure logs restriction queries.",
- "resource": "Users",
- "test": {
- "endpoint": "/v2/users/does-not-exist",
- "method": "PATCH",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "synthetics_private_location_read",
- "title": "Synthetics Private Locations Read",
- "description": "View, search, and use Synthetics private locations.",
- "resource": "Synthetics",
- "test": {
- "endpoint": "/v1/synthetics/private-locations/does-not-exit",
- "method": "GET",
- "valid_statuses": [200, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "synthetics_private_location_write",
- "title": "Synthetics Private Locations Write",
- "description": "Create and delete private locations in addition to having access to the associated installation guidelines.",
- "resource": "Synthetics",
- "test": {
- "endpoint": "/v1/synthetics/private-locations/does-not-exit",
- "method": "PUT",
- "valid_statuses": [200, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "usage_read",
- "title": "Usage Read",
- "description": "View your organization's usage and usage attribution.",
- "resource": "Usage Metering",
- "test": {
- "endpoint": "/v2/usage/hourly_usage",
- "method": "GET",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "metric_tags_write",
- "title": "Metric Tags Write",
- "description": "Edit and save tag configurations for custom metrics.",
- "resource": "Metrics",
- "test": {
- "endpoint": "/v2/metrics/does-not-exit/tags",
- "method": "POST",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "audit_logs_read",
- "title": "Audit Trail Read",
- "description": "View Audit Trail in your organization.",
- "resource": "Audit",
- "test": {
- "endpoint": "/v2/audit/events",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "api_keys_read",
- "title": "API Keys Read",
- "description": "List and retrieve the key values of all API Keys in your organization.",
- "resource": "Key Management",
- "test": {
- "endpoint": "/v2/api_keys",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "api_keys_write",
- "title": "API Keys Write",
- "description": "Create and rename API Keys for your organization.",
- "resource": "Key Management",
- "test": {
- "endpoint": "/v2/api_keys/does-not-exist",
- "method": "PATCH",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "synthetics_global_variable_read",
- "title": "Synthetics Global Variable Read",
- "description": "View, search, and use Synthetics global variables.",
- "resource": "Synthetics",
- "test": {
- "endpoint": "/v1/synthetics/variables",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "synthetics_global_variable_write",
- "title": "Synthetics Global Variable Write",
- "description": "Create, edit, and delete global variables for Synthetics.",
- "resource": "Synthetics",
- "test": {
- "endpoint": "/v1/synthetics/variables",
- "method": "POST",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "synthetics_read",
- "title": "Synthetics Read",
- "description": "List and view configured Synthetic tests and test results.",
- "resource": "Synthetics",
- "test": {
- "endpoint": "/v1/synthetics/tests",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "synthetics_write",
- "title": "Synthetics Write",
- "description": "Create, edit, and delete Synthetic tests.",
- "resource": "Synthetics",
- "test": {
- "endpoint": "/v1/synthetics/tests/mobile/does-not-exit",
- "method": "PUT",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "synthetics_default_settings_read",
- "title": "Synthetics Default Settings Read",
- "description": "View the default settings for Synthetic Monitoring.",
- "resource": "Synthetics",
- "test": {
- "endpoint": "/v1/synthetics/settings/default_locations",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "service_account_write",
- "title": "Service Account Write",
- "description": "Create, disable, and use Service Accounts in your organization.",
- "resource": "Service Accounts",
- "test": {
- "endpoint": "/v2/service_accounts/does-not-exist/application_keys",
- "method": "POST",
- "valid_statuses": [200, 400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "apm_read",
- "title": "APM Read",
- "description": "Read and query APM and Trace Analytics.",
- "resource": "APM",
- "test": {
- "endpoint": "/v2/apm/config/metrics",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "apm_retention_filter_read",
- "title": "APM Retention Filters Read",
- "description": "Read trace retention filters. A user with this permission can view the retention filters page, list of filters, their statistics, and creation info.",
- "resource": "APM",
- "test": {
- "endpoint": "/v2/apm/config/retention-filters/should-not-exist",
- "method": "GET",
- "valid_statuses": [200, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "apm_retention_filter_write",
- "title": "APM Retention Filters Write",
- "description": "Create, edit, and delete trace retention filters. A user with this permission can create new retention filters, and update or delete to existing retention filters.",
- "resource": "APM",
- "test": {
- "endpoint": "/v2/apm/config/retention-filters/should-not-exit",
- "method": "DELETE",
- "valid_statuses": [404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "rum_apps_write",
- "title": "RUM Apps Write",
- "description": "Create, edit, and delete RUM applications. Creating a RUM application automatically generates a Client Token. In order to create Client Tokens directly, a user needs the Client Tokens Write permission.",
- "resource": "RUM",
- "test": {
- "endpoint": "/v2/rum/applications/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "data_scanner_read",
- "title": "Data Scanner Read",
- "description": "View Sensitive Data Scanner configurations and scanning results.",
- "resource": "Sensitive Data Scanner",
- "test": {
- "endpoint": "/v2/sensitive-data-scanner/config/standard-patterns",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "data_scanner_write",
- "title": "Data Scanner Write",
- "description": "Edit Sensitive Data Scanner configurations.",
- "resource": "Sensitive Data Scanner",
- "test": {
- "endpoint": "/v2/sensitive-data-scanner/config/groups/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "org_management",
- "title": "Org Management",
- "description": "Edit org configurations, including authentication and certain security preferences such as configuring SAML, renaming an org, configuring allowed login methods, creating child orgs, subscribing & unsubscribing from apps in the marketplace, and enabling & disabling Remote Configuration for the entire organization.",
- "resource": "Organizations",
- "test": {
- "endpoint": "/v1/org",
- "method": "GET",
- "valid_statuses": [200, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "security_monitoring_filters_read",
- "title": "Security Filters Read",
- "description": "Read Security Filters.",
- "resource": "Security Monitoring",
- "test": {
- "endpoint": "/v2/security_monitoring/configuration/security_filters",
- "method": "GET",
- "valid_statuses": [200, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "security_monitoring_filters_write",
- "title": "Security Filters Write",
- "description": "Create, edit, and delete Security Filters.",
- "resource": "Security Monitoring",
- "test": {
- "endpoint": "/v2/security_monitoring/configuration/security_filters/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "incident_read",
- "title": "Incidents Read",
- "description": "View incidents in Datadog.",
- "resource": "Incidents",
- "test": {
- "endpoint": "/v2/incidents",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "incident_write",
- "title": "Incidents Write",
- "description": "Create, view, and manage incidents in Datadog.",
- "resource": "Incidents",
- "test": {
- "endpoint": "/v2/incidents/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "incident_settings_write",
- "title": "Incident Settings Write",
- "description": "Configure Incident Settings.",
- "resource": "Incidents",
- "test": {
- "endpoint": "/v2/incidents/config/types/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "rum_apps_read",
- "title": "RUM Apps Read",
- "description": "View RUM Applications data.",
- "resource": "RUM",
- "test": {
- "endpoint": "/v2/rum/applications",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "security_monitoring_notification_profiles_read",
- "title": "Security Notification Rules Read",
- "description": "Read Notification Rules.",
- "resource": "Security Monitoring",
- "test": {
- "endpoint": "/v2/security/signals/notification_rules",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "security_monitoring_notification_profiles_write",
- "title": "Security Notification Rules Write",
- "description": "Create, edit, and delete Notification Rules.",
- "resource": "Security Monitoring",
- "test": {
- "endpoint": "/v2/security/signals/notification_rules/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "apm_generate_metrics",
- "title": "APM Generate Metrics",
- "description": "Create custom metrics from spans.",
- "resource": "APM",
- "test": {
- "endpoint": "/v2/apm/config/metrics/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "apm_pipelines_write",
- "title": "APM Pipelines Write",
- "description": "Add and change APM pipeline configurations.",
- "resource": "APM",
- "test": {
- "endpoint": "/v2/apm/config/retention-filters/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "apm_pipelines_read",
- "title": "APM Pipelines Read",
- "description": "View APM pipeline configurations.",
- "resource": "APM",
- "test": {
- "endpoint": "/v2/apm/config/retention-filters",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "observability_pipelines_read",
- "title": "Observability Pipelines Read",
- "description": "View pipelines in your organization.",
- "resource": "Observability Pipelines",
- "test": {
- "endpoint": "/v2/remote_config/products/obs_pipelines/pipelines",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "workflows_read",
- "title": "Workflows Read",
- "description": "View workflows.",
- "resource": "Workflows",
- "test": {
- "endpoint": "/v2/workflows/does-not-exist",
- "method": "GET",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "workflows_write",
- "title": "Workflows Write",
- "description": "Create, edit, and delete workflows.",
- "resource": "Workflows",
- "test": {
- "endpoint": "/v2/workflows/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "workflows_run",
- "title": "Workflows Run",
- "description": "Run workflows.",
- "resource": "Workflows",
- "test": {
- "endpoint": "/v2/workflows/should-not-exist/instances",
- "method": "POST",
- "valid_statuses": [400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "connections_read",
- "title": "Connections Read",
- "description": "List and view available connections. Connections contain secrets that cannot be revealed.",
- "resource": "Connections",
- "test": {
- "endpoint": "/v2/actions/connections/does-not-exist",
- "method": "GET",
- "valid_statuses": [200, 400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "connections_write",
- "title": "Connections Write",
- "description": "Create and delete connections.",
- "resource": "Connections",
- "test": {
- "endpoint": "/v2/actions/connections/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "notebooks_read",
- "title": "Notebooks Read",
- "description": "View notebooks.",
- "resource": "Notebooks",
- "test": {
- "endpoint": "/v1/notebooks",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "notebooks_write",
- "title": "Notebooks Write",
- "description": "Create and change notebooks.",
- "resource": "Notebooks",
- "test": {
- "endpoint": "/v1/notebooks/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "aws_configurations_manage",
- "title": "AWS Configurations Manage",
- "description": "Add or remove but not edit AWS integration configurations.",
- "resource": "Integrations",
- "test": {
- "endpoint": "/v2/integration/aws/accounts/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "azure_configurations_manage",
- "title": "Azure Configurations Manage",
- "description": "Add or remove but not edit Azure integration configurations.",
- "resource": "Integrations",
- "test": {
- "endpoint": "/v1/integration/azure",
- "method": "DELETE",
- "valid_statuses": [400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "gcp_configurations_manage",
- "title": "GCP Configurations Manage",
- "description": "Add or remove but not edit GCP integration configurations.",
- "resource": "Integrations",
- "test": {
- "endpoint": "/v2/integration/gcp/accounts/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "manage_integrations",
- "title": "Integrations Manage",
- "description": "Install, uninstall, and configure integrations.",
- "resource": "Integrations",
- "test": {
- "endpoint": "/v2/integrations/cloudflare/accounts/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [400, 404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "slos_read",
- "title": "SLOs Read",
- "description": "View SLOs and status corrections.",
- "resource": "SLOs",
- "test": {
- "endpoint": "/v1/slo",
- "method": "GET",
- "valid_statuses": [200, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "slos_write",
- "title": "SLOs Write",
- "description": "Create, edit, and delete SLOs.",
- "resource": "SLOs",
- "test": {
- "endpoint": "/v1/slo/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [404, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "slos_corrections",
- "title": "SLOs Status Corrections",
- "description": "Apply, edit, and delete SLO status corrections. A user with this permission can make status corrections, even if they do not have permission to edit those SLOs.",
- "resource": "SLOs",
- "test": {
- "endpoint": "/v1/slo/correction",
- "method": "POST",
- "valid_statuses": [400, 429],
- "invalid_statuses": [403]
- }
- },
- {
- "name": "monitor_config_policy_write",
- "title": "Monitor Configuration Policy Write",
- "description": "Create, update, and delete monitor configuration policies.",
- "resource": "Monitors",
- "test": {
- "endpoint": "/v2/monitor/policy/does-not-exist",
- "method": "DELETE",
- "valid_statuses": [400, 404, 429],
- "invalid_statuses": [403]
- }
- }
-]
diff --git a/pkg/analyzer/analyzers/digitalocean/digitalocean.go b/pkg/analyzer/analyzers/digitalocean/digitalocean.go
deleted file mode 100644
index 3e3233ab38ff..000000000000
--- a/pkg/analyzer/analyzers/digitalocean/digitalocean.go
+++ /dev/null
@@ -1,303 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go digitalocean
-
-package digitalocean
-
-import (
- "bytes"
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "os"
- "sync"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-// to avoid rate limiting
-const MAX_CONCURRENT_TESTS = 10
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeDigitalOcean }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("missing key in credInfo")
- }
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeDigitalOcean,
- Metadata: nil,
- Bindings: make([]analyzers.Binding, len(info.Permissions)),
- }
-
- resource := analyzers.Resource{
- Name: info.User.Name,
- FullyQualifiedName: info.User.UUID,
- Type: "User",
- Metadata: map[string]any{
- "email": info.User.Email,
- "status": info.User.Status,
- },
- }
-
- for idx, permission := range info.Permissions {
- result.Bindings[idx] = analyzers.Binding{
- Resource: resource,
- Permission: analyzers.Permission{
- Value: permission,
- },
- }
- }
-
- return &result
-}
-
-//go:embed scopes.json
-var scopesConfig []byte
-
-type HttpStatusTest struct {
- Endpoint string `json:"endpoint"`
- Method string `json:"method"`
- Payload interface{} `json:"payload"`
- ValidStatuses []int `json:"valid_status_code"`
- InvalidStatuses []int `json:"invalid_status_code"`
-}
-
-func StatusContains(status int, vals []int) bool {
- for _, v := range vals {
- if status == v {
- return true
- }
- }
- return false
-}
-
-func (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string) (bool, error) {
- // If body data, marshal to JSON
- var data io.Reader
- if h.Payload != nil {
- jsonData, err := json.Marshal(h.Payload)
- if err != nil {
- return false, err
- }
- data = bytes.NewBuffer(jsonData)
- }
-
- client := analyzers.NewAnalyzeClient(cfg)
-
- req, err := http.NewRequest(h.Method, h.Endpoint, data)
- if err != nil {
- return false, err
- }
-
- // Add custom headers if provided
- for key, value := range headers {
- req.Header.Set(key, value)
- }
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer resp.Body.Close()
-
- // Check response status code
- switch {
- case StatusContains(resp.StatusCode, h.ValidStatuses):
- return true, nil
- case StatusContains(resp.StatusCode, h.InvalidStatuses):
- return false, nil
- default:
- return false, errors.New("error checking response status code")
- }
-}
-
-type Scope struct {
- Name string `json:"name"`
- HttpTest HttpStatusTest `json:"test"`
-}
-
-func readInScopes() ([]Scope, error) {
- var scopes []Scope
- if err := json.Unmarshal(scopesConfig, &scopes); err != nil {
- return nil, err
- }
-
- return scopes, nil
-}
-
-func checkPermissions(cfg *config.Config, key string) ([]string, error) {
- scopes, err := readInScopes()
- if err != nil {
- return nil, fmt.Errorf("reading in scopes: %w", err)
- }
-
- var (
- permissions = make([]string, 0, len(scopes))
- mu sync.Mutex
- wg sync.WaitGroup
- slots = make(chan struct{}, MAX_CONCURRENT_TESTS)
- errCh = make(chan error, 1)
- )
-
- for _, scope := range scopes {
- wg.Add(1)
- go func(scope Scope) {
- defer wg.Done()
-
- // acquire a slot
- slots <- struct{}{}
- defer func() { <-slots }()
-
- status, err := scope.HttpTest.RunTest(cfg, map[string]string{"Authorization": "Bearer " + key})
- if err != nil {
- // send first error and ignore the rest
- select {
- case errCh <- fmt.Errorf("Scope %s: %w", scope.Name, err):
- default:
- }
- return
- }
- if status {
- mu.Lock()
- permissions = append(permissions, scope.Name)
- mu.Unlock()
- }
- }(scope)
- }
-
- // wait for all goroutines to finish or an error to occur
- go func() {
- wg.Wait()
- close(errCh)
- }()
-
- if err := <-errCh; err != nil {
- return nil, err
- }
-
- return permissions, nil
-}
-
-type user struct {
- Email string `json:"email"`
- Name string `json:"name"`
- UUID string `json:"uuid"`
- Status string `json:"status"`
-}
-
-type userJSON struct {
- Account user `json:"account"`
-}
-
-func getUser(cfg *config.Config, token string) (*user, error) {
- // Create new HTTP request
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", "https://api.digitalocean.com/v2/account", nil)
- if err != nil {
- return nil, err
- }
-
- // Add custom headers if provided
- req.Header.Set("Authorization", "Bearer "+token)
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- switch resp.StatusCode {
- case http.StatusOK:
- // Decode response body
- var response userJSON
- err = json.NewDecoder(resp.Body).Decode(&response)
- if err != nil {
- return nil, err
- }
-
- return &response.Account, nil
- case http.StatusUnauthorized:
- return nil, errors.New("invalid token")
- default:
- return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-type SecretInfo struct {
- User user
- Permissions []string
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- color.Green("[!] Valid DigitalOcean API key\n\n")
-
- color.Yellow("[i] User: %s (%s)\n\n", info.User.Name, info.User.Email)
-
- printPermissions(info.Permissions)
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- var info = &SecretInfo{}
-
- user, err := getUser(cfg, key)
- if err != nil {
- return nil, err
- }
- info.User = *user
-
- permissions, err := checkPermissions(cfg, key)
- if err != nil {
- return nil, err
- }
-
- if len(permissions) == 0 {
- return nil, fmt.Errorf("invalid DigitalOcean API key")
- }
-
- info.Permissions = permissions
-
- return info, nil
-}
-
-func printPermissions(permissions []string) {
- color.Yellow("[i] Permissions:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission"})
- for _, permission := range permissions {
- t.AppendRow(table.Row{color.GreenString(permission)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/digitalocean/digitalocean_test.go b/pkg/analyzer/analyzers/digitalocean/digitalocean_test.go
deleted file mode 100644
index 894b3eb0ba97..000000000000
--- a/pkg/analyzer/analyzers/digitalocean/digitalocean_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package digitalocean
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid digitalocean key",
- key: testSecrets.MustGetField("DIGITALOCEAN_PERSONAL_ACCESS_TOKEN"),
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/digitalocean/expected_output.json b/pkg/analyzer/analyzers/digitalocean/expected_output.json
deleted file mode 100644
index 3f31c861b191..000000000000
--- a/pkg/analyzer/analyzers/digitalocean/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":26,"Bindings":[{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"action:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"app:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"billing:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"block_storage:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"cdn_endpoint:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"certificate:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"container_registry:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"database:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"domain:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"domain_record:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"droplet:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"droplet_autoscale_pool:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"firewall:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"floating_ip:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"genai_agent:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"image:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"kubernetes:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"load_balancer:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"monitoring:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"namespace:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"one_click:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"project:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"region:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"reserved_ip:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"size:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"snapshot:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"ssh_key:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"tag:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"uptime:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"vpc:read","Parent":null}},{"Resource":{"Name":"sevoma","FullyQualifiedName":"f87d96c58bcc938176acebb04ac9450bbe113cca","Type":"User","Metadata":{"email":"sevoma@gmail.com","status":"active"},"Parent":null},"Permission":{"Value":"vpc_peering:read","Parent":null}}],"UnboundedResources":null,"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/digitalocean/permissions.go b/pkg/analyzer/analyzers/digitalocean/permissions.go
deleted file mode 100644
index a168203e8b5b..000000000000
--- a/pkg/analyzer/analyzers/digitalocean/permissions.go
+++ /dev/null
@@ -1,546 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package digitalocean
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- OneClickRead Permission = iota
- OneClickCreate Permission = iota
- ActionRead Permission = iota
- AppRead Permission = iota
- AppCreate Permission = iota
- AppUpdate Permission = iota
- AppDelete Permission = iota
- BillingRead Permission = iota
- BlockStorageRead Permission = iota
- BlockStorageCreate Permission = iota
- BlockStorageDelete Permission = iota
- CdnEndpointRead Permission = iota
- CdnEndpointCreate Permission = iota
- CdnEndpointUpdate Permission = iota
- CdnEndpointDelete Permission = iota
- CertificateRead Permission = iota
- CertificateCreate Permission = iota
- CertificateDelete Permission = iota
- ContainerRegistryRead Permission = iota
- ContainerRegistryCreate Permission = iota
- DatabaseRead Permission = iota
- DatabaseCreate Permission = iota
- DatabaseUpdate Permission = iota
- DatabaseDelete Permission = iota
- DomainRecordRead Permission = iota
- DomainRecordCreate Permission = iota
- DomainRecordUpdate Permission = iota
- DomainRecordDelete Permission = iota
- DomainRead Permission = iota
- DomainCreate Permission = iota
- DomainDelete Permission = iota
- DropletRead Permission = iota
- DropletCreate Permission = iota
- DropletDelete Permission = iota
- DropletAutoscalePoolRead Permission = iota
- DropletAutoscalePoolCreate Permission = iota
- DropletAutoscalePoolUpdate Permission = iota
- DropletAutoscalePoolDelete Permission = iota
- FirewallRead Permission = iota
- FirewallCreate Permission = iota
- FirewallUpdate Permission = iota
- FirewallDelete Permission = iota
- FloatingIpRead Permission = iota
- FloatingIpCreate Permission = iota
- FloatingIpDelete Permission = iota
- NamespaceRead Permission = iota
- NamespaceCreate Permission = iota
- NamespaceDelete Permission = iota
- GenaiAgentRead Permission = iota
- GenaiAgentCreate Permission = iota
- GenaiAgentUpdate Permission = iota
- GenaiAgentDelete Permission = iota
- ImageRead Permission = iota
- ImageCreate Permission = iota
- ImageUpdate Permission = iota
- ImageDelete Permission = iota
- KubernetesRead Permission = iota
- KubernetesCreate Permission = iota
- KubernetesUpdate Permission = iota
- KubernetesDelete Permission = iota
- LoadBalancerRead Permission = iota
- LoadBalancerCreate Permission = iota
- LoadBalancerUpdate Permission = iota
- LoadBalancerDelete Permission = iota
- MonitoringRead Permission = iota
- MonitoringCreate Permission = iota
- MonitoringUpdate Permission = iota
- MonitoringDelete Permission = iota
- ProjectRead Permission = iota
- ProjectCreate Permission = iota
- ProjectUpdate Permission = iota
- ProjectDelete Permission = iota
- RegionRead Permission = iota
- ReservedIpRead Permission = iota
- ReservedIpCreate Permission = iota
- ReservedIpDelete Permission = iota
- SizeRead Permission = iota
- SnapshotRead Permission = iota
- SnapshotDelete Permission = iota
- SshKeyRead Permission = iota
- SshKeyCreate Permission = iota
- SshKeyUpdate Permission = iota
- SshKeyDelete Permission = iota
- TagRead Permission = iota
- TagCreate Permission = iota
- TagDelete Permission = iota
- UptimeRead Permission = iota
- UptimeCreate Permission = iota
- UptimeUpdate Permission = iota
- UptimeDelete Permission = iota
- VpcPeeringRead Permission = iota
- VpcPeeringCreate Permission = iota
- VpcPeeringUpdate Permission = iota
- VpcPeeringDelete Permission = iota
- VpcRead Permission = iota
- VpcCreate Permission = iota
- VpcUpdate Permission = iota
- VpcDelete Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- OneClickRead: "one_click:read",
- OneClickCreate: "one_click:create",
- ActionRead: "action:read",
- AppRead: "app:read",
- AppCreate: "app:create",
- AppUpdate: "app:update",
- AppDelete: "app:delete",
- BillingRead: "billing:read",
- BlockStorageRead: "block_storage:read",
- BlockStorageCreate: "block_storage:create",
- BlockStorageDelete: "block_storage:delete",
- CdnEndpointRead: "cdn_endpoint:read",
- CdnEndpointCreate: "cdn_endpoint:create",
- CdnEndpointUpdate: "cdn_endpoint:update",
- CdnEndpointDelete: "cdn_endpoint:delete",
- CertificateRead: "certificate:read",
- CertificateCreate: "certificate:create",
- CertificateDelete: "certificate:delete",
- ContainerRegistryRead: "container_registry:read",
- ContainerRegistryCreate: "container_registry:create",
- DatabaseRead: "database:read",
- DatabaseCreate: "database:create",
- DatabaseUpdate: "database:update",
- DatabaseDelete: "database:delete",
- DomainRecordRead: "domain_record:read",
- DomainRecordCreate: "domain_record:create",
- DomainRecordUpdate: "domain_record:update",
- DomainRecordDelete: "domain_record:delete",
- DomainRead: "domain:read",
- DomainCreate: "domain:create",
- DomainDelete: "domain:delete",
- DropletRead: "droplet:read",
- DropletCreate: "droplet:create",
- DropletDelete: "droplet:delete",
- DropletAutoscalePoolRead: "droplet_autoscale_pool:read",
- DropletAutoscalePoolCreate: "droplet_autoscale_pool:create",
- DropletAutoscalePoolUpdate: "droplet_autoscale_pool:update",
- DropletAutoscalePoolDelete: "droplet_autoscale_pool:delete",
- FirewallRead: "firewall:read",
- FirewallCreate: "firewall:create",
- FirewallUpdate: "firewall:update",
- FirewallDelete: "firewall:delete",
- FloatingIpRead: "floating_ip:read",
- FloatingIpCreate: "floating_ip:create",
- FloatingIpDelete: "floating_ip:delete",
- NamespaceRead: "namespace:read",
- NamespaceCreate: "namespace:create",
- NamespaceDelete: "namespace:delete",
- GenaiAgentRead: "genai_agent:read",
- GenaiAgentCreate: "genai_agent:create",
- GenaiAgentUpdate: "genai_agent:update",
- GenaiAgentDelete: "genai_agent:delete",
- ImageRead: "image:read",
- ImageCreate: "image:create",
- ImageUpdate: "image:update",
- ImageDelete: "image:delete",
- KubernetesRead: "kubernetes:read",
- KubernetesCreate: "kubernetes:create",
- KubernetesUpdate: "kubernetes:update",
- KubernetesDelete: "kubernetes:delete",
- LoadBalancerRead: "load_balancer:read",
- LoadBalancerCreate: "load_balancer:create",
- LoadBalancerUpdate: "load_balancer:update",
- LoadBalancerDelete: "load_balancer:delete",
- MonitoringRead: "monitoring:read",
- MonitoringCreate: "monitoring:create",
- MonitoringUpdate: "monitoring:update",
- MonitoringDelete: "monitoring:delete",
- ProjectRead: "project:read",
- ProjectCreate: "project:create",
- ProjectUpdate: "project:update",
- ProjectDelete: "project:delete",
- RegionRead: "region:read",
- ReservedIpRead: "reserved_ip:read",
- ReservedIpCreate: "reserved_ip:create",
- ReservedIpDelete: "reserved_ip:delete",
- SizeRead: "size:read",
- SnapshotRead: "snapshot:read",
- SnapshotDelete: "snapshot:delete",
- SshKeyRead: "ssh_key:read",
- SshKeyCreate: "ssh_key:create",
- SshKeyUpdate: "ssh_key:update",
- SshKeyDelete: "ssh_key:delete",
- TagRead: "tag:read",
- TagCreate: "tag:create",
- TagDelete: "tag:delete",
- UptimeRead: "uptime:read",
- UptimeCreate: "uptime:create",
- UptimeUpdate: "uptime:update",
- UptimeDelete: "uptime:delete",
- VpcPeeringRead: "vpc_peering:read",
- VpcPeeringCreate: "vpc_peering:create",
- VpcPeeringUpdate: "vpc_peering:update",
- VpcPeeringDelete: "vpc_peering:delete",
- VpcRead: "vpc:read",
- VpcCreate: "vpc:create",
- VpcUpdate: "vpc:update",
- VpcDelete: "vpc:delete",
- }
-
- StringToPermission = map[string]Permission{
- "one_click:read": OneClickRead,
- "one_click:create": OneClickCreate,
- "action:read": ActionRead,
- "app:read": AppRead,
- "app:create": AppCreate,
- "app:update": AppUpdate,
- "app:delete": AppDelete,
- "billing:read": BillingRead,
- "block_storage:read": BlockStorageRead,
- "block_storage:create": BlockStorageCreate,
- "block_storage:delete": BlockStorageDelete,
- "cdn_endpoint:read": CdnEndpointRead,
- "cdn_endpoint:create": CdnEndpointCreate,
- "cdn_endpoint:update": CdnEndpointUpdate,
- "cdn_endpoint:delete": CdnEndpointDelete,
- "certificate:read": CertificateRead,
- "certificate:create": CertificateCreate,
- "certificate:delete": CertificateDelete,
- "container_registry:read": ContainerRegistryRead,
- "container_registry:create": ContainerRegistryCreate,
- "database:read": DatabaseRead,
- "database:create": DatabaseCreate,
- "database:update": DatabaseUpdate,
- "database:delete": DatabaseDelete,
- "domain_record:read": DomainRecordRead,
- "domain_record:create": DomainRecordCreate,
- "domain_record:update": DomainRecordUpdate,
- "domain_record:delete": DomainRecordDelete,
- "domain:read": DomainRead,
- "domain:create": DomainCreate,
- "domain:delete": DomainDelete,
- "droplet:read": DropletRead,
- "droplet:create": DropletCreate,
- "droplet:delete": DropletDelete,
- "droplet_autoscale_pool:read": DropletAutoscalePoolRead,
- "droplet_autoscale_pool:create": DropletAutoscalePoolCreate,
- "droplet_autoscale_pool:update": DropletAutoscalePoolUpdate,
- "droplet_autoscale_pool:delete": DropletAutoscalePoolDelete,
- "firewall:read": FirewallRead,
- "firewall:create": FirewallCreate,
- "firewall:update": FirewallUpdate,
- "firewall:delete": FirewallDelete,
- "floating_ip:read": FloatingIpRead,
- "floating_ip:create": FloatingIpCreate,
- "floating_ip:delete": FloatingIpDelete,
- "namespace:read": NamespaceRead,
- "namespace:create": NamespaceCreate,
- "namespace:delete": NamespaceDelete,
- "genai_agent:read": GenaiAgentRead,
- "genai_agent:create": GenaiAgentCreate,
- "genai_agent:update": GenaiAgentUpdate,
- "genai_agent:delete": GenaiAgentDelete,
- "image:read": ImageRead,
- "image:create": ImageCreate,
- "image:update": ImageUpdate,
- "image:delete": ImageDelete,
- "kubernetes:read": KubernetesRead,
- "kubernetes:create": KubernetesCreate,
- "kubernetes:update": KubernetesUpdate,
- "kubernetes:delete": KubernetesDelete,
- "load_balancer:read": LoadBalancerRead,
- "load_balancer:create": LoadBalancerCreate,
- "load_balancer:update": LoadBalancerUpdate,
- "load_balancer:delete": LoadBalancerDelete,
- "monitoring:read": MonitoringRead,
- "monitoring:create": MonitoringCreate,
- "monitoring:update": MonitoringUpdate,
- "monitoring:delete": MonitoringDelete,
- "project:read": ProjectRead,
- "project:create": ProjectCreate,
- "project:update": ProjectUpdate,
- "project:delete": ProjectDelete,
- "region:read": RegionRead,
- "reserved_ip:read": ReservedIpRead,
- "reserved_ip:create": ReservedIpCreate,
- "reserved_ip:delete": ReservedIpDelete,
- "size:read": SizeRead,
- "snapshot:read": SnapshotRead,
- "snapshot:delete": SnapshotDelete,
- "ssh_key:read": SshKeyRead,
- "ssh_key:create": SshKeyCreate,
- "ssh_key:update": SshKeyUpdate,
- "ssh_key:delete": SshKeyDelete,
- "tag:read": TagRead,
- "tag:create": TagCreate,
- "tag:delete": TagDelete,
- "uptime:read": UptimeRead,
- "uptime:create": UptimeCreate,
- "uptime:update": UptimeUpdate,
- "uptime:delete": UptimeDelete,
- "vpc_peering:read": VpcPeeringRead,
- "vpc_peering:create": VpcPeeringCreate,
- "vpc_peering:update": VpcPeeringUpdate,
- "vpc_peering:delete": VpcPeeringDelete,
- "vpc:read": VpcRead,
- "vpc:create": VpcCreate,
- "vpc:update": VpcUpdate,
- "vpc:delete": VpcDelete,
- }
-
- PermissionIDs = map[Permission]int{
- OneClickRead: 1,
- OneClickCreate: 2,
- ActionRead: 3,
- AppRead: 4,
- AppCreate: 5,
- AppUpdate: 6,
- AppDelete: 7,
- BillingRead: 8,
- BlockStorageRead: 9,
- BlockStorageCreate: 10,
- BlockStorageDelete: 11,
- CdnEndpointRead: 12,
- CdnEndpointCreate: 13,
- CdnEndpointUpdate: 14,
- CdnEndpointDelete: 15,
- CertificateRead: 16,
- CertificateCreate: 17,
- CertificateDelete: 18,
- ContainerRegistryRead: 19,
- ContainerRegistryCreate: 20,
- DatabaseRead: 21,
- DatabaseCreate: 22,
- DatabaseUpdate: 23,
- DatabaseDelete: 24,
- DomainRecordRead: 25,
- DomainRecordCreate: 26,
- DomainRecordUpdate: 27,
- DomainRecordDelete: 28,
- DomainRead: 29,
- DomainCreate: 30,
- DomainDelete: 31,
- DropletRead: 32,
- DropletCreate: 33,
- DropletDelete: 34,
- DropletAutoscalePoolRead: 35,
- DropletAutoscalePoolCreate: 36,
- DropletAutoscalePoolUpdate: 37,
- DropletAutoscalePoolDelete: 38,
- FirewallRead: 39,
- FirewallCreate: 40,
- FirewallUpdate: 41,
- FirewallDelete: 42,
- FloatingIpRead: 43,
- FloatingIpCreate: 44,
- FloatingIpDelete: 45,
- NamespaceRead: 46,
- NamespaceCreate: 47,
- NamespaceDelete: 48,
- GenaiAgentRead: 49,
- GenaiAgentCreate: 50,
- GenaiAgentUpdate: 51,
- GenaiAgentDelete: 52,
- ImageRead: 53,
- ImageCreate: 54,
- ImageUpdate: 55,
- ImageDelete: 56,
- KubernetesRead: 57,
- KubernetesCreate: 58,
- KubernetesUpdate: 59,
- KubernetesDelete: 60,
- LoadBalancerRead: 61,
- LoadBalancerCreate: 62,
- LoadBalancerUpdate: 63,
- LoadBalancerDelete: 64,
- MonitoringRead: 65,
- MonitoringCreate: 66,
- MonitoringUpdate: 67,
- MonitoringDelete: 68,
- ProjectRead: 69,
- ProjectCreate: 70,
- ProjectUpdate: 71,
- ProjectDelete: 72,
- RegionRead: 73,
- ReservedIpRead: 74,
- ReservedIpCreate: 75,
- ReservedIpDelete: 76,
- SizeRead: 77,
- SnapshotRead: 78,
- SnapshotDelete: 79,
- SshKeyRead: 80,
- SshKeyCreate: 81,
- SshKeyUpdate: 82,
- SshKeyDelete: 83,
- TagRead: 84,
- TagCreate: 85,
- TagDelete: 86,
- UptimeRead: 87,
- UptimeCreate: 88,
- UptimeUpdate: 89,
- UptimeDelete: 90,
- VpcPeeringRead: 91,
- VpcPeeringCreate: 92,
- VpcPeeringUpdate: 93,
- VpcPeeringDelete: 94,
- VpcRead: 95,
- VpcCreate: 96,
- VpcUpdate: 97,
- VpcDelete: 98,
- }
-
- IdToPermission = map[int]Permission{
- 1: OneClickRead,
- 2: OneClickCreate,
- 3: ActionRead,
- 4: AppRead,
- 5: AppCreate,
- 6: AppUpdate,
- 7: AppDelete,
- 8: BillingRead,
- 9: BlockStorageRead,
- 10: BlockStorageCreate,
- 11: BlockStorageDelete,
- 12: CdnEndpointRead,
- 13: CdnEndpointCreate,
- 14: CdnEndpointUpdate,
- 15: CdnEndpointDelete,
- 16: CertificateRead,
- 17: CertificateCreate,
- 18: CertificateDelete,
- 19: ContainerRegistryRead,
- 20: ContainerRegistryCreate,
- 21: DatabaseRead,
- 22: DatabaseCreate,
- 23: DatabaseUpdate,
- 24: DatabaseDelete,
- 25: DomainRecordRead,
- 26: DomainRecordCreate,
- 27: DomainRecordUpdate,
- 28: DomainRecordDelete,
- 29: DomainRead,
- 30: DomainCreate,
- 31: DomainDelete,
- 32: DropletRead,
- 33: DropletCreate,
- 34: DropletDelete,
- 35: DropletAutoscalePoolRead,
- 36: DropletAutoscalePoolCreate,
- 37: DropletAutoscalePoolUpdate,
- 38: DropletAutoscalePoolDelete,
- 39: FirewallRead,
- 40: FirewallCreate,
- 41: FirewallUpdate,
- 42: FirewallDelete,
- 43: FloatingIpRead,
- 44: FloatingIpCreate,
- 45: FloatingIpDelete,
- 46: NamespaceRead,
- 47: NamespaceCreate,
- 48: NamespaceDelete,
- 49: GenaiAgentRead,
- 50: GenaiAgentCreate,
- 51: GenaiAgentUpdate,
- 52: GenaiAgentDelete,
- 53: ImageRead,
- 54: ImageCreate,
- 55: ImageUpdate,
- 56: ImageDelete,
- 57: KubernetesRead,
- 58: KubernetesCreate,
- 59: KubernetesUpdate,
- 60: KubernetesDelete,
- 61: LoadBalancerRead,
- 62: LoadBalancerCreate,
- 63: LoadBalancerUpdate,
- 64: LoadBalancerDelete,
- 65: MonitoringRead,
- 66: MonitoringCreate,
- 67: MonitoringUpdate,
- 68: MonitoringDelete,
- 69: ProjectRead,
- 70: ProjectCreate,
- 71: ProjectUpdate,
- 72: ProjectDelete,
- 73: RegionRead,
- 74: ReservedIpRead,
- 75: ReservedIpCreate,
- 76: ReservedIpDelete,
- 77: SizeRead,
- 78: SnapshotRead,
- 79: SnapshotDelete,
- 80: SshKeyRead,
- 81: SshKeyCreate,
- 82: SshKeyUpdate,
- 83: SshKeyDelete,
- 84: TagRead,
- 85: TagCreate,
- 86: TagDelete,
- 87: UptimeRead,
- 88: UptimeCreate,
- 89: UptimeUpdate,
- 90: UptimeDelete,
- 91: VpcPeeringRead,
- 92: VpcPeeringCreate,
- 93: VpcPeeringUpdate,
- 94: VpcPeeringDelete,
- 95: VpcRead,
- 96: VpcCreate,
- 97: VpcUpdate,
- 98: VpcDelete,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/digitalocean/permissions.yaml b/pkg/analyzer/analyzers/digitalocean/permissions.yaml
deleted file mode 100644
index 4272686936ff..000000000000
--- a/pkg/analyzer/analyzers/digitalocean/permissions.yaml
+++ /dev/null
@@ -1,99 +0,0 @@
-permissions:
- - one_click:read
- - one_click:create
- - action:read
- - app:read
- - app:create
- - app:update
- - app:delete
- - billing:read
- - block_storage:read
- - block_storage:create
- - block_storage:delete
- - cdn_endpoint:read
- - cdn_endpoint:create
- - cdn_endpoint:update
- - cdn_endpoint:delete
- - certificate:read
- - certificate:create
- - certificate:delete
- - container_registry:read
- - container_registry:create
- - database:read
- - database:create
- - database:update
- - database:delete
- - domain_record:read
- - domain_record:create
- - domain_record:update
- - domain_record:delete
- - domain:read
- - domain:create
- - domain:delete
- - droplet:read
- - droplet:create
- - droplet:delete
- - droplet_autoscale_pool:read
- - droplet_autoscale_pool:create
- - droplet_autoscale_pool:update
- - droplet_autoscale_pool:delete
- - firewall:read
- - firewall:create
- - firewall:update
- - firewall:delete
- - floating_ip:read
- - floating_ip:create
- - floating_ip:delete
- - namespace:read
- - namespace:create
- - namespace:delete
- - genai_agent:read
- - genai_agent:create
- - genai_agent:update
- - genai_agent:delete
- - image:read
- - image:create
- - image:update
- - image:delete
- - kubernetes:read
- - kubernetes:create
- - kubernetes:update
- - kubernetes:delete
- - load_balancer:read
- - load_balancer:create
- - load_balancer:update
- - load_balancer:delete
- - monitoring:read
- - monitoring:create
- - monitoring:update
- - monitoring:delete
- - project:read
- - project:create
- - project:update
- - project:delete
- - region:read
- - reserved_ip:read
- - reserved_ip:create
- - reserved_ip:delete
- - size:read
- - snapshot:read
- - snapshot:delete
- - ssh_key:read
- - ssh_key:create
- - ssh_key:update
- - ssh_key:delete
- - tag:read
- - tag:create
- - tag:delete
- - uptime:read
- - uptime:create
- - uptime:update
- - uptime:delete
- - vpc_peering:read
- - vpc_peering:create
- - vpc_peering:update
- - vpc_peering:delete
- - vpc:read
- - vpc:create
- - vpc:update
- - vpc:delete
diff --git a/pkg/analyzer/analyzers/digitalocean/scopes.json b/pkg/analyzer/analyzers/digitalocean/scopes.json
deleted file mode 100644
index 9037a5a61034..000000000000
--- a/pkg/analyzer/analyzers/digitalocean/scopes.json
+++ /dev/null
@@ -1,884 +0,0 @@
-[
- {
- "name": "one_click:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/1-clicks",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "one_click:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/1-clicks/kubernetes",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "action:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/actions",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "app:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/apps",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "app:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/apps",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "app:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/apps/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "app:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/apps/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "billing:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/customers/my/balance",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "block_storage:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/volumes",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "block_storage:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/volumes",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "block_storage:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/volumes/0000",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "cdn_endpoint:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/cdn/endpoints",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "cdn_endpoint:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/cdn/endpoints",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "cdn_endpoint:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/cdn/endpoints/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "cdn_endpoint:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/cdn/endpoints/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "certificate:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/certificates",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "certificate:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/certificates",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "certificate:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/certificates/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "container_registry:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/registry",
- "method": "GET",
- "valid_status_code": [200, 404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "container_registry:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/registry",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "database:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/databases",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "database:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/databases",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "database:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/databases/`nowaythisidcanexist/config",
- "method": "PATCH",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "database:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/databases/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "domain_record:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/domains/`nowaythisdomaincanexist.com/records",
- "method": "GET",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "domain_record:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/domains/`nowaythisdomaincanexist.com/records",
- "method": "POST",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "domain_record:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/domains/`nowaythisdomaincanexist.com/records/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "domain_record:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/domains/`nowaythisdomaincanexist.com/records/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "domain:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/domains",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "domain:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/domains",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "domain:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/domains/`nowaythisdomaincanexist.com",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "droplet:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/droplets",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "droplet:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/droplets",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "droplet:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/droplets/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "droplet_autoscale_pool:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/droplets/autoscale",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "droplet_autoscale_pool:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/droplets/autoscale",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "droplet_autoscale_pool:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/droplets/autoscale/0d3db13e-a604-4944-9827-7ec2642d32ac",
- "method": "PUT",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "droplet_autoscale_pool:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/droplets/autoscale/0d3db13e-a604-4944-9827-7ec2642d32ac",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "firewall:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/firewalls",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "firewall:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/firewalls",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "firewall:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/firewalls/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "firewall:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/firewalls/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "floating_ip:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/floating_ips",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "floating_ip:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/floating_ips",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "floating_ip:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/floating_ips/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "namespace:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/functions/namespaces",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "namespace:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/functions/namespaces",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "namespace:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/functions/namespaces/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "genai_agent:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/gen-ai/agents",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "genai_agent:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/gen-ai/agents",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "genai_agent:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/gen-ai/agents/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "genai_agent:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/gen-ai/agents/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "image:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/images",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "image:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/images",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "image:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/images/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "image:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/images/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "kubernetes:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/kubernetes/clusters",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "kubernetes:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/kubernetes/clusters",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "kubernetes:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/kubernetes/clusters/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "kubernetes:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/kubernetes/clusters/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "load_balancer:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/load_balancers",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "load_balancer:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/load_balancers",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "load_balancer:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/load_balancers/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "load_balancer:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/load_balancers/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "monitoring:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/monitoring/alerts",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "monitoring:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/monitoring/alerts",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "monitoring:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/monitoring/alerts/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "monitoring:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/monitoring/alerts/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "project:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/projects",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "project:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/projects",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "project:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/projects/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "project:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/projects/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "region:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/regions",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "reserved_ip:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/reserved_ips",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "reserved_ip:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/reserved_ips",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "reserved_ip:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/reserved_ips/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "size:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/sizes",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "snapshot:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/snapshots",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "snapshot:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/snapshots/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "ssh_key:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/account/keys",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "ssh_key:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/account/keys",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "ssh_key:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/account/keys/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "ssh_key:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/account/keys/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "tag:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/tags",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "tag:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/tags",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "tag:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/tags/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "uptime:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/uptime/checks",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "uptime:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/uptime/checks",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "uptime:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/uptime/checks/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "uptime:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/uptime/checks/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "vpc_peering:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/vpc_peerings",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "vpc_peering:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/vpc_peerings",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "vpc_peering:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/vpc_peerings/5a4981aa-9653-4bd1-bef5-d6bff52042e4",
- "method": "PATCH",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "vpc_peering:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/vpc_peerings/5a4981aa-9653-4bd1-bef5-d6bff52042e4",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "vpc:read",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/vpcs",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "vpc:create",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/vpcs",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "vpc:update",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/vpcs/`nowaythisidcanexist",
- "method": "PUT",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "vpc:delete",
- "test": {
- "endpoint": "https://api.digitalocean.com/v2/vpcs/`nowaythisidcanexist",
- "method": "DELETE",
- "valid_status_code": [404],
- "invalid_status_code": [403]
- }
- }
-]
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/dockerhub/dockerhub.go b/pkg/analyzer/analyzers/dockerhub/dockerhub.go
deleted file mode 100644
index b83df3119c93..000000000000
--- a/pkg/analyzer/analyzers/dockerhub/dockerhub.go
+++ /dev/null
@@ -1,192 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go dockerhub
-package dockerhub
-
-import (
- "errors"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-// SecretInfo hold the information about the token generated from username and pat
-type SecretInfo struct {
- User User
- Valid bool
- Reference string
- Permissions []string
- Repositories []Repository
- ExpiresIn string
- Misc map[string]string
-}
-
-// User hold the information about user to whom the personal access token belongs
-type User struct {
- ID string
- Username string
- Email string
-}
-
-// Repository hold information about each repository the user can access
-type Repository struct {
- ID string
- Name string
- Type string
- IsPrivate bool
- StarCount int
- PullCount int
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeDockerHub
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- username, exist := credInfo["username"]
- if !exist {
- return nil, errors.New("username not found in the credentials info")
- }
-
- pat, exist := credInfo["pat"]
- if !exist {
- return nil, errors.New("personal access token(PAT) not found in the credentials info")
- }
-
- info, err := AnalyzePermissions(a.Cfg, username, pat)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-// AnalyzePermissions will collect all the scopes assigned to token along with resource it can access
-func AnalyzePermissions(cfg *config.Config, username, pat string) (*SecretInfo, error) {
- // create the http client
- client := analyzers.NewAnalyzeClientUnrestricted(cfg) // `/user/login` is a non-safe request
-
- var secretInfo = &SecretInfo{}
-
- // try to login and get jwt token
- token, err := login(client, username, pat)
- if err != nil {
- return nil, err
- }
-
- if err := decodeTokenToSecretInfo(token, secretInfo); err != nil {
- return nil, err
- }
-
- // fetch repositories using the jwt token and translate them to secret info
- if err := fetchRepositories(client, username, token, secretInfo); err != nil {
- return nil, err
- }
-
- // return secret info
- return secretInfo, nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, username, pat string) {
- info, err := AnalyzePermissions(cfg, username, pat)
- if err != nil {
- // just print the error in cli and continue as a partial success
- color.Red("[x] Error : %s", err.Error())
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- if info.Valid {
- color.Green("[!] Valid DockerHub Credentials\n\n")
- // print user information
- printUser(info.User)
- // print permissions
- printPermissions(info.Permissions)
- // print repositories
- printRepositories(info.Repositories)
-
- color.Yellow("\n[i] Expires: %s", info.ExpiresIn)
- }
-}
-
-// secretInfoToAnalyzerResult translate secret info to Analyzer Result
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeDockerHub,
- Metadata: map[string]any{"Valid_Key": info.Valid},
- Bindings: make([]analyzers.Binding, len(info.Repositories)),
- }
-
- // extract information to create bindings and append to result bindings
- for _, repo := range info.Repositories {
- binding := analyzers.Binding{
- Resource: analyzers.Resource{
- Name: repo.Name,
- FullyQualifiedName: repo.ID,
- Type: repo.Type,
- Metadata: map[string]any{
- "is_private": repo.IsPrivate,
- "pull_count": repo.PullCount,
- "star_count": repo.StarCount,
- },
- },
- Permission: analyzers.Permission{
- // as all permissions are against repo, we assign the highest available permission
- Value: assignHighestPermission(info.Permissions),
- },
- }
-
- result.Bindings = append(result.Bindings, binding)
- }
-
- return &result
-}
-
-// cli print functions
-func printUser(user User) {
- color.Green("\n[i] User:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"ID", "Username", "Email"})
- t.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Username), color.GreenString(user.Email)})
- t.Render()
-}
-
-func printPermissions(permissions []string) {
- color.Yellow("[i] Permissions:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission"})
- for _, permission := range permissions {
- t.AppendRow(table.Row{color.GreenString(permission)})
- }
- t.Render()
-}
-
-func printRepositories(repos []Repository) {
- color.Green("\n[i] Repositories:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Type", "ID(username/repo/repo_type/repo_name)", "Name", "Is Private", "Pull Count", "Star Count"})
- for _, repo := range repos {
- t.AppendRow(table.Row{color.GreenString(repo.Type), color.GreenString(repo.ID), color.GreenString(repo.Name),
- color.GreenString("%t", repo.IsPrivate), color.GreenString("%d", repo.PullCount), color.GreenString("%d", repo.StarCount)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/dockerhub/dockerhub_test.go b/pkg/analyzer/analyzers/dockerhub/dockerhub_test.go
deleted file mode 100644
index 6cc37749c0ef..000000000000
--- a/pkg/analyzer/analyzers/dockerhub/dockerhub_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package dockerhub
-
-import (
- _ "embed"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed result_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- username := testSecrets.MustGetField("DOCKERHUB_USERNAME")
- pat := testSecrets.MustGetField("DOCKERHUB_PAT")
-
- tests := []struct {
- name string
- username string
- pat string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid dockerhub credentials",
- username: username,
- pat: pat,
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"username": tt.username, "pat": tt.pat})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/dockerhub/helper.go b/pkg/analyzer/analyzers/dockerhub/helper.go
deleted file mode 100644
index 62b17d8e19d4..000000000000
--- a/pkg/analyzer/analyzers/dockerhub/helper.go
+++ /dev/null
@@ -1,146 +0,0 @@
-package dockerhub
-
-import (
- "errors"
- "fmt"
- "sort"
- "time"
-
- "github.com/golang-jwt/jwt/v5"
-)
-
-// permission hierarchy - always keep from highest permission to lowest
-var permissionHierarchy = []string{"repo:admin", "repo:write", "repo:read", "repo:public_read"}
-
-// precompute a ranking map for the ranking approach.
-// lower index means higher permission.
-var permissionRank = func() map[string]int {
- rank := make(map[string]int, len(permissionHierarchy))
- // loop over permissions hierarchy to assign index to each permission
- // as hierarchy start from highest to lowest, the 0 index will be assigned to highest possible permission and n will be lowest possible permission
- for i, perm := range permissionHierarchy {
- rank[perm] = i
- }
-
- // return the rank map with indexed permissions
- return rank
-}()
-
-// decodeTokenToSecretInfo decode the jwt token and add the information to secret info
-func decodeTokenToSecretInfo(jwtToken string, secretInfo *SecretInfo) error {
- type userClaims struct {
- ID string `json:"uuid"`
- Username string `json:"username"`
- Email string `json:"email"`
- }
-
- type hubJwtClaims struct {
- Scope string `json:"scope"`
- HubClaims userClaims `json:"https://hub.docker.com"`
- ExpiresIn int `json:"exp"`
- jwt.RegisteredClaims
- }
-
- parser := jwt.NewParser()
- token, _, err := parser.ParseUnverified(jwtToken, &hubJwtClaims{})
- if err != nil {
- return err
- }
-
- if claims, ok := token.Claims.(*hubJwtClaims); ok {
- secretInfo.User = User{
- ID: claims.HubClaims.ID,
- Username: claims.HubClaims.Username,
- Email: claims.HubClaims.Email,
- }
-
- secretInfo.ExpiresIn = humandReadableTime(claims.ExpiresIn)
-
- secretInfo.Permissions = append(secretInfo.Permissions, claims.Scope)
- secretInfo.Valid = true
-
- return nil
- }
-
- return errors.New("failed to parse claims")
-}
-
-// repositoriesToSecretInfo translate repositories to secretInfo after sorting them
-func repositoriesToSecretInfo(username string, repos *RepositoriesResponse, secretInfo *SecretInfo) {
- // sort the repositories first
- sortRepositories(repos)
-
- for _, repo := range repos.Result {
- secretInfo.Repositories = append(secretInfo.Repositories, Repository{
- // as repositories does not have a unique key, we make one by combining multiple fields
- ID: fmt.Sprintf("%s/repo/%s/%s", username, repo.Type, repo.Name), // e.g: user123/repo/image/repo1
- Name: repo.Name,
- Type: repo.Type,
- IsPrivate: repo.IsPrivate,
- StarCount: repo.StarCount,
- PullCount: repo.PullCount,
- })
- }
-}
-
-/*
-sortRepositories sort the repositories as following
-
-private:
- - pullcount(descending)
- - starcount(descending)
-
-public:
- - pullcount(descending)
- - starcount(descending)
-*/
-func sortRepositories(repos *RepositoriesResponse) {
- sort.SliceStable(repos.Result, func(i, j int) bool {
- a, b := repos.Result[i], repos.Result[j]
-
- // prioritize private repositories over public
- if a.IsPrivate != b.IsPrivate {
- return a.IsPrivate
- }
-
- // sort by Pull Count (descending)
- if a.PullCount != b.PullCount {
- return a.PullCount > b.PullCount
- }
-
- // sort by Star Count (descending)
- return a.StarCount > b.StarCount
- })
-}
-
-// assignHighestPermission selects the highest available permission
-func assignHighestPermission(permissions []string) string {
- bestRank := len(permissionHierarchy)
- bestPerm := ""
- for _, perm := range permissions {
- // check in indexes permissions
- if rank, ok := permissionRank[perm]; ok {
- // early exit if highest permission is found.
- if rank == 0 {
- return perm
- }
-
- if rank < bestRank {
- bestRank = rank
- bestPerm = perm
- }
- }
- }
-
- return bestPerm
-
-}
-
-// humandReadableTime converts seconds to days, hours, minutes, or seconds based on the value
-func humandReadableTime(seconds int) string {
- // Convert Unix timestamp to time.Time object
- t := time.Unix(int64(seconds), 0)
-
- // Format the time as "March 2" (Month Day format)
- return t.Format("January 2, 2006")
-}
diff --git a/pkg/analyzer/analyzers/dockerhub/permissions.go b/pkg/analyzer/analyzers/dockerhub/permissions.go
deleted file mode 100644
index d012c5abb793..000000000000
--- a/pkg/analyzer/analyzers/dockerhub/permissions.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package dockerhub
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- RepoRead Permission = iota
- RepoWrite Permission = iota
- RepoAdmin Permission = iota
- RepoPublicRead Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- RepoRead: "repo:read",
- RepoWrite: "repo:write",
- RepoAdmin: "repo:admin",
- RepoPublicRead: "repo:public_read",
- }
-
- StringToPermission = map[string]Permission{
- "repo:read": RepoRead,
- "repo:write": RepoWrite,
- "repo:admin": RepoAdmin,
- "repo:public_read": RepoPublicRead,
- }
-
- PermissionIDs = map[Permission]int{
- RepoRead: 1,
- RepoWrite: 2,
- RepoAdmin: 3,
- RepoPublicRead: 4,
- }
-
- IdToPermission = map[int]Permission{
- 1: RepoRead,
- 2: RepoWrite,
- 3: RepoAdmin,
- 4: RepoPublicRead,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/dockerhub/permissions.yaml b/pkg/analyzer/analyzers/dockerhub/permissions.yaml
deleted file mode 100644
index ca6cafc039be..000000000000
--- a/pkg/analyzer/analyzers/dockerhub/permissions.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-permissions:
-- repo:read
-- repo:write
-- repo:admin
-- repo:public_read
diff --git a/pkg/analyzer/analyzers/dockerhub/requests.go b/pkg/analyzer/analyzers/dockerhub/requests.go
deleted file mode 100644
index 17d95e6e8fca..000000000000
--- a/pkg/analyzer/analyzers/dockerhub/requests.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package dockerhub
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strings"
-)
-
-// LoginResponse is the successful response from the /login API
-type LoginResponse struct {
- Token string `json:"token"`
-}
-
-// ErrorLoginResponse is the error response from the /login API
-type ErrorLoginResponse struct {
- Detail string `json:"detail"`
- Login2FAToken string `json:"login_2fa_token"` // if login require 2FA authentication
-}
-
-// RepositoriesResponse is the /repositories/ response
-type RepositoriesResponse struct {
- Result []struct {
- Name string `json:"name"`
- Type string `json:"repository_type"`
- IsPrivate bool `json:"is_private"`
- StarCount int `json:"star_count"`
- PullCount int `json:"pull_count"`
- } `json:"results"`
-}
-
-// login call the /login api with username and jwt token and if successful retrieve the token string and return
-func login(client *http.Client, username, pat string) (string, error) {
- payload := strings.NewReader(fmt.Sprintf(`{"username": "%s", "password": "%s"}`, username, pat))
-
- req, err := http.NewRequest(http.MethodPost, "https://hub.docker.com/v2/users/login", payload)
- if err != nil {
- return "", err
- }
-
- req.Header.Add("Content-Type", "application/json")
- resp, err := client.Do(req)
- if err != nil {
- return "", err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- var token LoginResponse
- if err := json.NewDecoder(resp.Body).Decode(&token); err != nil {
- return "", err
- }
-
- return token.Token, nil
- case http.StatusUnauthorized:
- var errorLogin ErrorLoginResponse
- if err := json.NewDecoder(resp.Body).Decode(&errorLogin); err != nil {
- return "", err
- }
-
- if errorLogin.Login2FAToken != "" {
- // TODO: handle it more appropriately
- return "", errors.New("valid credentials; account require 2fa authentication")
- }
-
- return "", errors.New(errorLogin.Detail)
- default:
- return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-// fetchRepositories call /repositories/ API
-func fetchRepositories(client *http.Client, username, token string, secretInfo *SecretInfo) error {
- req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://hub.docker.com/v2/repositories/%s", username), http.NoBody)
- if err != nil {
- return err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", "Bearer "+token)
- resp, err := client.Do(req)
- if err != nil {
- return err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- var repositories RepositoriesResponse
-
- if err := json.NewDecoder(resp.Body).Decode(&repositories); err != nil {
- return err
- }
-
- // translate repositories response to secretInfo
- repositoriesToSecretInfo(username, &repositories, secretInfo)
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- // the token is valid and this shall never happen because the least scope a token can have is repo:public_read.
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d; while fetching repositories information", resp.StatusCode)
-
- }
-}
diff --git a/pkg/analyzer/analyzers/dockerhub/result_output.json b/pkg/analyzer/analyzers/dockerhub/result_output.json
deleted file mode 100644
index eb8d178dd3b1..000000000000
--- a/pkg/analyzer/analyzers/dockerhub/result_output.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
- "AnalyzerType": 4,
- "Bindings": [
- {
- "Resource": {
- "Name": "test-private",
- "FullyQualifiedName": "truffledockerman/repo/image/test-private",
- "Type": "image",
- "Metadata": {
- "is_private": true,
- "pull_count": 0,
- "star_count": 0
- },
- "Parent": null
- },
- "Permission": {
- "Value": "repo:admin",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "test",
- "FullyQualifiedName": "truffledockerman/repo/image/test",
- "Type": "image",
- "Metadata": {
- "is_private": false,
- "pull_count": 0,
- "star_count": 0
- },
- "Parent": null
- },
- "Permission": {
- "Value": "repo:admin",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {
- "Valid_Key": true
- }
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/dropbox/dropbox.go b/pkg/analyzer/analyzers/dropbox/dropbox.go
deleted file mode 100644
index 684df1c2c91b..000000000000
--- a/pkg/analyzer/analyzers/dropbox/dropbox.go
+++ /dev/null
@@ -1,354 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go dropbox
-package dropbox
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "os"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- _ "embed"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-//go:embed scopes.json
-var scopeConfigJson []byte
-
-type Analyzer struct {
- Cfg *config.Config
-}
-type PermissionStatus string
-
-const (
- StatusGranted PermissionStatus = "Granted"
- StatusDenied PermissionStatus = "Denied"
- StatusUnverified PermissionStatus = "Unverified"
-)
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeDropbox
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- token, exist := credInfo["token"]
- if !exist {
- return nil, errors.New("token not found in credentials info")
- }
-
- info, err := AnalyzePermissions(a.Cfg, token)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, token string) {
- info, err := AnalyzePermissions(cfg, token)
- if err != nil {
- color.Red("[x] Invalid Dropbox Token\n")
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- color.Green("[i] Valid Dropbox OAuth2 Credentials\n")
- printAccountAndPermissions(info)
-}
-
-func AnalyzePermissions(cfg *config.Config, token string) (*secretInfo, error) {
- // Dropbox API uses POST requests for all requests, so we need to use an unrestricted client
- client := analyzers.NewAnalyzeClientUnrestricted(cfg)
- scopeConfigMap, err := getScopeConfigMap()
- if err != nil {
- return nil, err
- }
-
- secretInfo := &secretInfo{}
-
- accountInfoPermission := PermissionStrings[AccountInfoRead]
- for _, perm := range PermissionStrings {
- scopeDetails := scopeConfigMap.Scopes[perm]
- status := StatusUnverified
- if perm == accountInfoPermission {
- // Account Info Read permission is always enabled
- status = StatusGranted
- }
- secretInfo.Permissions = append(secretInfo.Permissions, accountPermission{
- Name: perm,
- Status: status,
- Actions: scopeDetails.Actions,
- })
- }
-
- if err := populateAccountInfo(client, secretInfo, token); err != nil {
- return nil, err
- }
-
- if err := testAllPermissions(client, secretInfo, scopeConfigMap, token); err != nil {
- return nil, err
- }
-
- return secretInfo, nil
-}
-
-func populateAccountInfo(client *http.Client, info *secretInfo, token string) error {
- endpoint := "/2/users/get_current_account"
- body, statusCode, err := callDropboxAPIEndpoint(client, endpoint, token)
- if err != nil {
- return err
- }
- switch statusCode {
- case http.StatusOK:
- if err := json.Unmarshal([]byte(body), &info.Account); err != nil {
- return fmt.Errorf("failed to unmarshal account info: %w", err)
- }
- return nil
- default:
- return fmt.Errorf("failed to validate scope. Status %d: %s", statusCode, body)
- }
-}
-
-func testAllPermissions(client *http.Client, info *secretInfo, scopeConfigMap *scopeConfig, token string) error {
- permissionStatuses := make(map[string]PermissionStatus)
-
- for _, perm := range PermissionStrings {
- scopeDetails := scopeConfigMap.Scopes[perm]
-
- if _, ok := permissionStatuses[perm]; ok || scopeDetails.TestEndpoint == "" {
- // Skip if the scope has already been determined or has no test endpoint
- continue
- }
-
- if perm == PermissionStrings[Openid] {
- // The OpenID permission can be validated using the "/2/users/get_current_account" endpoint
- // If the response contains the "email" key, that implies that the "email" permission is also granted
- // Similar case for the "given_name" key and the "profile" permission
- body, statusCode, err := callDropboxAPIEndpoint(client, scopeDetails.TestEndpoint, token)
- if err != nil {
- return err
- }
- switch statusCode {
- case http.StatusOK, http.StatusConflict:
- // The endpoint responds with 409 Conflict if the openid scope
- // is granted but the email and profile scopes are not granted
- permissionStatuses[perm] = StatusGranted
-
- // Check for the "email" key in the response body
- if strings.Contains(body, "\"email\":") {
- permissionStatuses[PermissionStrings[Email]] = StatusGranted
- } else {
- permissionStatuses[PermissionStrings[Email]] = StatusDenied
- }
-
- // Check for the "given_name" key in the response body
- if strings.Contains(body, "\"given_name\":") {
- permissionStatuses[PermissionStrings[Profile]] = StatusGranted
- } else {
- permissionStatuses[PermissionStrings[Profile]] = StatusDenied
- }
- case http.StatusUnauthorized:
- permissionStatuses[perm] = StatusDenied
- permissionStatuses[PermissionStrings[Email]] = StatusDenied
- permissionStatuses[PermissionStrings[Profile]] = StatusDenied
- }
- continue
- }
-
- isGranted, err := testPermission(client, scopeDetails.TestEndpoint, token)
- if err != nil {
- return err
- }
-
- if !isGranted {
- permissionStatuses[perm] = StatusDenied
- continue
- }
-
- permissionStatuses[perm] = StatusGranted
- for _, impliedScope := range scopeDetails.ImpliedScopes {
- permissionStatuses[impliedScope] = StatusGranted
- }
- }
-
- for idx, permission := range info.Permissions {
- permission.Status = permissionStatuses[permission.Name]
- info.Permissions[idx] = permission
- }
-
- return nil
-}
-
-func testPermission(client *http.Client, testEndpoint string, token string) (bool, error) {
- body, statusCode, err := callDropboxAPIEndpoint(client, testEndpoint, token)
- if err != nil {
- return false, err
- }
-
- switch statusCode {
- case http.StatusUnauthorized:
- return false, nil
- case http.StatusBadRequest:
- if strings.Contains(body, "does not have the required scope") {
- return false, nil
- }
- if strings.Contains(body, "your request body is empty") {
- return true, nil
- }
- }
- return false, fmt.Errorf("failed to validate scope. Status %d: %s", statusCode, body)
-}
-
-func callDropboxAPIEndpoint(client *http.Client, endpoint string, token string) (string, int, error) {
- baseURL := "https://api.dropboxapi.com"
- req, err := http.NewRequest(http.MethodPost, baseURL+endpoint, nil)
- if err != nil {
- return "", 0, err
- }
- req.Header.Set("Authorization", "Bearer "+token)
- res, err := client.Do(req)
- if err != nil {
- return "", 0, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return "", 0, fmt.Errorf("failed to read response body: %w", err)
- }
-
- return string(bodyBytes), res.StatusCode, nil
-}
-
-func getScopeConfigMap() (*scopeConfig, error) {
- var scopeConfigMap scopeConfig
- if err := json.Unmarshal(scopeConfigJson, &scopeConfigMap); err != nil {
- return nil, errors.New("failed to unmarshal scopes.json: " + err.Error())
- }
- return &scopeConfigMap, nil
-}
-
-func secretInfoToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- account := info.Account
- accountID := account.AccountID
- allPermissions := getValidatedPermissions(info)
-
- resource := analyzers.Resource{
- Name: fmt.Sprintf("%s %s", account.Name.GivenName, account.Name.Surname),
- FullyQualifiedName: accountID,
- Type: "account",
- Metadata: map[string]any{
- "email": account.Email,
- "emailVerified": account.EmailVerified,
- "disabled": account.Disabled,
- "country": account.Country,
- "accountType": account.AccountType.Tag,
- },
- }
- analyzers.BindAllPermissions(resource, allPermissions...)
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeDropbox,
- Metadata: nil,
- Bindings: analyzers.BindAllPermissions(resource, allPermissions...),
- }
- return &result
-}
-
-func getValidatedPermissions(info *secretInfo) []analyzers.Permission {
- permissions := []analyzers.Permission{}
-
- for _, permission := range info.Permissions {
- if permission.Status != StatusGranted {
- continue
- }
- permissions = append(permissions, analyzers.Permission{
- Value: permission.Name,
- })
- }
-
- return permissions
-}
-
-func printAccountAndPermissions(info *secretInfo) {
- color.Yellow("\n[i] Accounts Info:")
- t1 := table.NewWriter()
- t1.SetOutputMirror(os.Stdout)
- t1.AppendHeader(table.Row{"ID", "Name", "Email", "Email Verified", "Disabled", "Country", "Account Type"})
- emailVerified := "No"
- disabled := "No"
- if info.Account.EmailVerified {
- emailVerified = "Yes"
- }
- if info.Account.Disabled {
- disabled = "Yes"
- }
- t1.AppendRow(table.Row{
- color.GreenString(info.Account.AccountID),
- color.GreenString(info.Account.Name.GivenName + " " + info.Account.Name.Surname),
- color.GreenString(info.Account.Email),
- color.GreenString(emailVerified),
- color.GreenString(disabled),
- color.GreenString(info.Account.Country),
- color.GreenString(info.Account.AccountType.Tag),
- })
- t1.SetOutputMirror(os.Stdout)
- t1.Render()
-
- color.Yellow("\n[i] Permissions:")
- t2 := table.NewWriter()
- t2.AppendHeader(table.Row{"Permission", "Access", "Actions"})
-
- permissions := info.Permissions
- for _, permission := range permissions {
- access := "Denied"
- permissionStatus := permission.Status
- if permissionStatus == StatusGranted {
- access = "Granted"
- }
- if permissionStatus == StatusUnverified {
- access = "Unverified"
- }
- for idx, action := range permission.Actions {
- permissionCell := ""
- accessCell := ""
- if idx == 0 {
- permissionCell = color.GreenString(permission.Name)
- accessCell = color.GreenString(access)
- }
-
- t2.AppendRow(table.Row{
- permissionCell,
- accessCell,
- action,
- })
- }
- t2.AppendSeparator()
- }
-
- t2.SetOutputMirror(os.Stdout)
- t2.Render()
- fmt.Printf("%s: https://www.dropbox.com/developers/documentation\n\n", color.GreenString("Ref"))
-}
diff --git a/pkg/analyzer/analyzers/dropbox/dropbox_test.go b/pkg/analyzer/analyzers/dropbox/dropbox_test.go
deleted file mode 100644
index 2dfc0b510bdb..000000000000
--- a/pkg/analyzer/analyzers/dropbox/dropbox_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package dropbox
-
-import (
- _ "embed"
- "encoding/json"
- "fmt"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- token := testSecrets.MustGetField("DROPBOX")
-
- tests := []struct {
- name string
- secret string
- want string
- wantErr bool
- }{
- {
- name: "valid dropbox credentials",
- secret: token,
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{
- "token": tt.secret,
- })
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- fmt.Println(string(gotJSON))
-
- // compare the JSON strings
- if string(gotJSON) != string(tt.want) {
- // pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(tt.want, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/dropbox/expected_output.json b/pkg/analyzer/analyzers/dropbox/expected_output.json
deleted file mode 100644
index a2a21098bb6d..000000000000
--- a/pkg/analyzer/analyzers/dropbox/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":40,"Bindings":[{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"accounts_info.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"files.metadata.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"sharing.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"contacts.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"files.content.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"sharing.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"contacts.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"files.metadata.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"files.content.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"openid","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"file_requests.read","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"file_requests.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"account_info.write","Parent":null}},{"Resource":{"Name":"Truffle Detectors","FullyQualifiedName":"dbid:AACfhSAzNq2rEGFtyIKeEchJumee8_A8Iq0","Type":"account","Metadata":{"accountType":"basic","country":"PK","disabled":false,"email":"detectors@trufflesec.com","emailVerified":true},"Parent":null},"Permission":{"Value":"profile","Parent":null}}],"UnboundedResources":null,"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/dropbox/models.go b/pkg/analyzer/analyzers/dropbox/models.go
deleted file mode 100644
index 30df28f35c27..000000000000
--- a/pkg/analyzer/analyzers/dropbox/models.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package dropbox
-
-type scopeConfig struct {
- Scopes map[string]scope `json:"scopes"`
-}
-
-type scope struct {
- TestEndpoint string `json:"test_endpoint"`
- ImpliedScopes []string `json:"implied_scopes"`
- Actions []string `json:"actions"`
-}
-
-type account struct {
- AccountID string `json:"account_id"`
- Name name `json:"name"`
- Email string `json:"email"`
- EmailVerified bool `json:"email_verified"`
- Disabled bool `json:"disabled"`
- Country string `json:"country"`
- AccountType accountType `json:"account_type"`
-}
-
-type accountType struct {
- Tag string `json:".tag"`
-}
-
-type name struct {
- GivenName string `json:"given_name"`
- Surname string `json:"surname"`
-}
-
-type accountPermission struct {
- Name string
- Status PermissionStatus
- Actions []string
-}
-
-type secretInfo struct {
- Account account
- Permissions []accountPermission
-}
diff --git a/pkg/analyzer/analyzers/dropbox/permissions.go b/pkg/analyzer/analyzers/dropbox/permissions.go
deleted file mode 100644
index 42831ab48f41..000000000000
--- a/pkg/analyzer/analyzers/dropbox/permissions.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package dropbox
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- AccountInfoWrite Permission = iota
- AccountInfoRead Permission = iota
- FilesMetadataWrite Permission = iota
- FilesMetadataRead Permission = iota
- FilesContentWrite Permission = iota
- FilesContentRead Permission = iota
- SharingWrite Permission = iota
- SharingRead Permission = iota
- FileRequestsWrite Permission = iota
- FileRequestsRead Permission = iota
- ContactsWrite Permission = iota
- ContactsRead Permission = iota
- Openid Permission = iota
- Profile Permission = iota
- Email Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- AccountInfoWrite: "account_info.write",
- AccountInfoRead: "account_info.read",
- FilesMetadataWrite: "files.metadata.write",
- FilesMetadataRead: "files.metadata.read",
- FilesContentWrite: "files.content.write",
- FilesContentRead: "files.content.read",
- SharingWrite: "sharing.write",
- SharingRead: "sharing.read",
- FileRequestsWrite: "file_requests.write",
- FileRequestsRead: "file_requests.read",
- ContactsWrite: "contacts.write",
- ContactsRead: "contacts.read",
- Openid: "openid",
- Profile: "profile",
- Email: "email",
- }
-
- StringToPermission = map[string]Permission{
- "account_info.write": AccountInfoWrite,
- "account_info.read": AccountInfoRead,
- "files.metadata.write": FilesMetadataWrite,
- "files.metadata.read": FilesMetadataRead,
- "files.content.write": FilesContentWrite,
- "files.content.read": FilesContentRead,
- "sharing.write": SharingWrite,
- "sharing.read": SharingRead,
- "file_requests.write": FileRequestsWrite,
- "file_requests.read": FileRequestsRead,
- "contacts.write": ContactsWrite,
- "contacts.read": ContactsRead,
- "openid": Openid,
- "profile": Profile,
- "email": Email,
- }
-
- PermissionIDs = map[Permission]int{
- AccountInfoWrite: 1,
- AccountInfoRead: 2,
- FilesMetadataWrite: 3,
- FilesMetadataRead: 4,
- FilesContentWrite: 5,
- FilesContentRead: 6,
- SharingWrite: 7,
- SharingRead: 8,
- FileRequestsWrite: 9,
- FileRequestsRead: 10,
- ContactsWrite: 11,
- ContactsRead: 12,
- Openid: 13,
- Profile: 14,
- Email: 15,
- }
-
- IdToPermission = map[int]Permission{
- 1: AccountInfoWrite,
- 2: AccountInfoRead,
- 3: FilesMetadataWrite,
- 4: FilesMetadataRead,
- 5: FilesContentWrite,
- 6: FilesContentRead,
- 7: SharingWrite,
- 8: SharingRead,
- 9: FileRequestsWrite,
- 10: FileRequestsRead,
- 11: ContactsWrite,
- 12: ContactsRead,
- 13: Openid,
- 14: Profile,
- 15: Email,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/dropbox/permissions.yaml b/pkg/analyzer/analyzers/dropbox/permissions.yaml
deleted file mode 100644
index a2d05594ac00..000000000000
--- a/pkg/analyzer/analyzers/dropbox/permissions.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-permissions:
- - account_info.write
- - account_info.read
- - files.metadata.write
- - files.metadata.read
- - files.content.write
- - files.content.read
- - sharing.write
- - sharing.read
- - file_requests.write
- - file_requests.read
- - contacts.write
- - contacts.read
- - openid
- - profile
- - email
diff --git a/pkg/analyzer/analyzers/dropbox/scopes.json b/pkg/analyzer/analyzers/dropbox/scopes.json
deleted file mode 100644
index 0494793a9a61..000000000000
--- a/pkg/analyzer/analyzers/dropbox/scopes.json
+++ /dev/null
@@ -1,147 +0,0 @@
-{
- "scopes": {
- "account_info.write": {
- "test_endpoint": "/2/account/set_profile_photo",
- "actions": [
- "Set a user's profile photo"
- ]
- },
- "account_info.read": {
- "test_endpoint": "/2/account/set_profile_photo",
- "actions": [
- "Validate user access token",
- "Get a list of feature values for the current account",
- "Get information about the current user's account",
- "Get the space usage information for the current user's account"
- ]
- },
- "files.metadata.write": {
- "test_endpoint": "/2/file_properties/properties/add",
- "implied_scopes": [
- "files.metadata.read"
- ],
- "actions": [
- "Add, update or remove property groups associated with files",
- "Add, update or remove properties associated with files and templates",
- "Add, update or remove templates associated with a user",
- "Add or remove tags from items"
- ]
- },
- "files.metadata.read": {
- "test_endpoint": "/2/file_properties/properties/search",
- "actions": [
- "Search across property templates for particular property field values",
- "Get the schema for a specified template",
- "Get the template identifiers for a team",
- "Get the metadata for a file or folder",
- "Get files, revisions, and folder contents",
- "Monitor for file changes",
- "Get tags from items",
- "Get file metadata",
- "Get user templates",
- "Get user Paper docs"
- ]
- },
- "files.content.write": {
- "test_endpoint": "/2/files/copy_v2",
- "implied_scopes": [
- "files.metadata.read"
- ],
- "actions": [
- "Add, update, move, or remove files",
- "Add, update, move, or remove folders",
- "Upload file content",
- "Lock/unlock files for writing",
- "Restore files to previous versions",
- "Add, update, or archive Paper docs",
- "Save URLs to Dropbox"
- ]
- },
- "files.content.read": {
- "test_endpoint": "/2/files/get_file_lock_batch",
- "actions": [
- "Export or download files",
- "Get lock information for files and folders",
- "Get file previews",
- "Stream file content",
- "Get image file thumbnails",
- "Export or download Paper docs"
- ]
- },
- "sharing.write": {
- "test_endpoint": "/2/sharing/add_file_member",
- "implied_scopes": [
- "sharing.read"
- ],
- "actions": [
- "Add, update, or remove file members",
- "Add, update, or remove folder members",
- "Get status of all asynchronous jobs",
- "Add, update, or remove shared links",
- "Share or unshare folders",
- "Add, update, or remove shared folder access policies",
- "Mount or unmount folders",
- "Add or remove users from Paper docs"
- ]
- },
- "sharing.read": {
- "test_endpoint": "/2/sharing/get_file_metadata",
- "actions": [
- "Get file metadata",
- "Get folder metadata",
- "Get shared link metadata",
- "Get file members",
- "Get folder members",
- "Get shared files",
- "Get shared folders",
- "Get mountable shared folders",
- "Get shared links",
- "Get information about the user's account",
- "Get file and folder information for Paper doc",
- "Get all users with Paper doc access"
- ]
- },
- "file_requests.write": {
- "test_endpoint": "/2/file_requests/update",
- "implied_scopes": [
- "file_requests.read"
- ],
- "actions": [
- "Add, update, or remove file requests"
- ]
- },
- "file_requests.read": {
- "test_endpoint": "/2/file_requests/list/continue",
- "actions": [
- "Get file requests",
- "Get file request count"
- ]
- },
- "contacts.write": {
- "test_endpoint": "/2/contacts/delete_manual_contacts_batch",
- "implied_scopes": [
- "contacts.read"
- ],
- "actions": [
- "Remove manually added contacts"
- ]
- },
- "contacts.read": {},
- "openid": {
- "test_endpoint": "/2/openid/userinfo",
- "actions": [
- "Get OpenID Connect user info"
- ]
- },
- "profile": {
- "actions": [
- "Get name in user info"
- ]
- },
- "email": {
- "actions": [
- "Get email address in user info"
- ]
- }
- }
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go
deleted file mode 100644
index dcd185e7b3f7..000000000000
--- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs.go
+++ /dev/null
@@ -1,468 +0,0 @@
-package elevenlabs
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "os"
- "slices"
- "strings"
- "sync"
-
- "github.com/fatih/color"
- "github.com/google/uuid"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-// SecretInfo hold information about key
-type SecretInfo struct {
- User User // the owner of key
- Valid bool
- Reference string
- Permissions []string // list of Permissions assigned to the key
- ElevenLabsResources []ElevenLabsResource // list of resources the key has access to
- mu sync.RWMutex
-}
-
-// AppendPermission safely append new permission to secret info permissions list.
-func (s *SecretInfo) AppendPermission(perm string) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- s.Permissions = append(s.Permissions, perm)
-}
-
-// HasPermission safely read secret info permission list to check if passed permission exist in the list.
-func (s *SecretInfo) HasPermission(perm Permission) bool {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- permissionString, _ := perm.ToString()
-
- return slices.Contains(s.Permissions, permissionString)
-}
-
-// AppendResource safely append new resource to secret info elevenlabs resource list.
-func (s *SecretInfo) AppendResource(resource ElevenLabsResource) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- s.ElevenLabsResources = append(s.ElevenLabsResources, resource)
-}
-
-// User hold the information about user to whom the key belongs to
-type User struct {
- ID string
- Name string
- SubscriptionTier string
- SubscriptionStatus string
-}
-
-// ElevenLabsResource hold information about the elevenlabs resource the key has access
-type ElevenLabsResource struct {
- ID string
- Name string
- Type string
- Metadata map[string]string
- Permission string
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeElevenLabs
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- // check if the `key` exist in the credentials info
- key, exist := credInfo["key"]
- if !exist {
- return nil, errors.New("key not found in credentials info")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-// AnalyzePermissions check if key is valid and analyzes the permission for the key
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- // create http client
- client := analyzers.NewAnalyzeClient(cfg)
-
- var secretInfo = &SecretInfo{}
-
- // fetch user information using the key
- user, err := fetchUser(client, key)
- if err != nil {
- return nil, err
- }
-
- secretInfo.Valid = true
-
- // if user is not nil, that means the key has user read permission. Set the user information in secret info user
- // user can only be nil when the key is valid but it does not have a user read permission
- if user != nil {
- elevenLabsUserToSecretInfoUser(*user, secretInfo)
- }
-
- // get elevenlabs resources with permissions
- if err := getElevenLabsResources(client, key, secretInfo); err != nil {
- return nil, err
- }
-
- return secretInfo, nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- // just print the error in cli and continue as a partial success
- color.Red("[x] Error : %s", err.Error())
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- if info.Valid {
- color.Green("[!] Valid ElevenLabs API key\n\n")
- // print user information
- printUser(info.User)
- // print permissions
- printPermissions(info.Permissions)
- // print resources
- printElevenLabsResources(info.ElevenLabsResources)
-
- color.Yellow("\n[i] Expires: Never")
- }
-}
-
-// secretInfoToAnalyzerResult translate secret info to Analyzer Result
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeElevenLabs,
- Metadata: map[string]any{},
- Bindings: make([]analyzers.Binding, 0),
- }
-
- // for resources to be uniquely identified, we need a unique id to be appended in resource fully qualified name
- uniqueId := info.User.ID
- if uniqueId == "" {
- uniqueId = uuid.NewString()
- }
-
- // extract information from resource to create bindings and append to result bindings
- for _, resource := range info.ElevenLabsResources {
- // if resource has permission it is binded resource
- if resource.Permission != "" {
- binding := analyzers.Binding{
- Resource: analyzers.Resource{
- Name: resource.Name,
- FullyQualifiedName: fmt.Sprintf("%s/%s/%s", uniqueId, resource.Type, resource.ID), // e.g: /Model/eleven_flash_v2_5
- Type: resource.Type,
- Metadata: map[string]any{}, // to avoid panic
- },
- Permission: analyzers.Permission{
- Value: resource.Permission,
- },
- }
-
- for key, value := range resource.Metadata {
- binding.Resource.Metadata[key] = value
- }
-
- result.Bindings = append(result.Bindings, binding)
- } else {
- // if resource is missing permission it is an unbounded resource
- unboundedResource := analyzers.Resource{
- Name: resource.Name,
- FullyQualifiedName: fmt.Sprintf("%s/%s/%s", uniqueId, resource.Type, resource.ID),
- Type: resource.Type,
- Metadata: map[string]any{},
- }
-
- for key, value := range resource.Metadata {
- unboundedResource.Metadata[key] = value
- }
-
- result.UnboundedResources = append(result.UnboundedResources, unboundedResource)
- }
- }
-
- result.Metadata["Valid_Key"] = info.Valid
-
- return &result
-}
-
-// fetchUser fetch elevenlabs user information associated with the key
-func fetchUser(client *http.Client, key string) (*User, error) {
- response, statusCode, err := makeElevenLabsRequest(client, permissionToAPIMap[UserRead], http.MethodGet, key)
- if err != nil {
- return nil, err
- }
-
- switch statusCode {
- case http.StatusOK:
- var user UserResponse
-
- if err := json.Unmarshal(response, &user); err != nil {
- return nil, err
- }
-
- return &User{
- ID: user.UserID,
- Name: user.FirstName,
- SubscriptionTier: user.Subscription.Tier,
- SubscriptionStatus: user.Subscription.Status,
- }, nil
- case http.StatusUnauthorized:
- var errorResp ErrorResponse
-
- if err := json.Unmarshal(response, &errorResp); err != nil {
- return nil, err
- }
-
- if errorResp.Detail.Status == InvalidAPIKey || errorResp.Detail.Status == NotVerifiable {
- return nil, errors.New("invalid api key")
- } else if errorResp.Detail.Status == MissingPermissions {
- // key is missing user read permissions but is valid
- return nil, nil
- }
-
- return nil, nil
- default:
- return nil, fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// elevenLabsUserToSecretInfoUser set the elevenlabs user information to secretInfo user
-func elevenLabsUserToSecretInfoUser(user User, secretInfo *SecretInfo) {
- secretInfo.User = user
- // add user read scope to secret info
- secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[UserRead])
- // map resource to secret info
- // as user is accessible through a specific permission and has a unique id it is also a resource
- secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{
- ID: user.ID,
- Name: user.Name,
- Type: "User",
- Permission: PermissionStrings[UserRead],
- })
-}
-
-/*
-getElevenLabsResources gather resources the key can access
-
-Note: The permissions in eleven labs is either Read or Read and Write. There is not separate permission for Write.
-*/
-func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretInfo) error {
- var (
- aggregatedErrs = make([]string, 0)
- errChan = make(chan error, 17) // buffer for 17 errors - one per API call
- wg sync.WaitGroup
- )
-
- // history
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- if err := getHistory(client, key, secretInfo); err != nil {
- errChan <- err
- }
-
- if err := deleteHistory(client, key, secretInfo); err != nil {
- errChan <- err
- }
- }()
-
- // dubbings
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- if err := deleteDubbing(client, key, secretInfo); err != nil {
- errChan <- err
- }
-
- // if dubbing write permission was not added
- if !secretInfo.HasPermission(DubbingWrite) {
- if err := getDebugging(client, key, secretInfo); err != nil {
- errChan <- err
- }
- }
- }()
-
- // voices
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- if err := getVoices(client, key, secretInfo); err != nil {
- errChan <- err
- }
-
- if err := deleteVoice(client, key, secretInfo); err != nil {
- errChan <- err
- }
- }()
-
- // projects
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- if err := getProjects(client, key, secretInfo); err != nil {
- errChan <- err
- }
-
- if err := deleteProject(client, key, secretInfo); err != nil {
- errChan <- err
- }
- }()
-
- // pronunciation dictionaries
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- if err := getPronunciationDictionaries(client, key, secretInfo); err != nil {
- errChan <- err
- }
-
- if err := removePronunciationDictionariesRule(client, key, secretInfo); err != nil {
- errChan <- err
- }
- }()
-
- // models
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- if err := getModels(client, key, secretInfo); err != nil {
- errChan <- err
- }
- }()
-
- // audio native
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- if err := updateAudioNativeProject(client, key, secretInfo); err != nil {
- errChan <- err
- }
- }()
-
- // workspace
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- if err := deleteInviteFromWorkspace(client, key, secretInfo); err != nil {
- errChan <- err
- }
- }()
-
- // speech
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- if err := textToSpeech(client, key, secretInfo); err != nil {
- errChan <- err
- }
-
- // voice changer
- if err := speechToSpeech(client, key, secretInfo); err != nil {
- errChan <- err
- }
- }()
-
- // audio isolation
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- if err := audioIsolation(client, key, secretInfo); err != nil {
- errChan <- err
- }
- }()
-
- // agent
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- // each agent can have a conversations which we get inside this function
- if err := getAgents(client, key, secretInfo); err != nil {
- errChan <- err
- }
- }()
-
- // wait for all API calls to finish
- wg.Wait()
- close(errChan)
-
- // collect all errors
- for err := range errChan {
- aggregatedErrs = append(aggregatedErrs, err.Error())
- }
-
- if len(aggregatedErrs) > 0 {
- return errors.New(strings.Join(aggregatedErrs, ", "))
- }
-
- return nil
-}
-
-// cli print functions
-func printUser(user User) {
- color.Green("\n[i] User:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"ID", "Name", "Subscription Tier", "Subscription Status"})
- t.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Name), color.GreenString(user.SubscriptionTier), color.GreenString(user.SubscriptionStatus)})
- t.Render()
-}
-
-func printPermissions(permissions []string) {
- color.Yellow("[i] Permissions:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission"})
- for _, permission := range permissions {
- t.AppendRow(table.Row{color.GreenString(permission)})
- }
- t.Render()
-}
-
-func printElevenLabsResources(resources []ElevenLabsResource) {
- color.Green("\n[i] Resources:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Resource Type", "Resource ID", "Resource Name", "Permission"})
- for _, resource := range resources {
- t.AppendRow(table.Row{color.GreenString(resource.Type), color.GreenString(resource.ID), color.GreenString(resource.Name), color.GreenString(resource.Permission)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go b/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go
deleted file mode 100644
index 772542177848..000000000000
--- a/pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package elevenlabs
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed result_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- key := testSecrets.MustGetField("ELEVENLABS")
-
- tests := []struct {
- name string
- key string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid ElevenLabs full access key",
- key: key,
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/elevenlabs/permissions.go b/pkg/analyzer/analyzers/elevenlabs/permissions.go
deleted file mode 100644
index e08445987017..000000000000
--- a/pkg/analyzer/analyzers/elevenlabs/permissions.go
+++ /dev/null
@@ -1,151 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package elevenlabs
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- TextToSpeech Permission = iota
- SpeechToSpeech Permission = iota
- AudioIsolation Permission = iota
- DubbingRead Permission = iota
- DubbingWrite Permission = iota
- ProjectsRead Permission = iota
- ProjectsWrite Permission = iota
- AudioNativeRead Permission = iota
- AudioNativeWrite Permission = iota
- PronunciationDictionariesRead Permission = iota
- PronunciationDictionariesWrite Permission = iota
- VoicesRead Permission = iota
- VoicesWrite Permission = iota
- ModelsRead Permission = iota
- SpeechHistoryRead Permission = iota
- SpeechHistoryWrite Permission = iota
- UserRead Permission = iota
- WorkspaceRead Permission = iota
- WorkspaceWrite Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- TextToSpeech: "text_to_speech",
- SpeechToSpeech: "speech_to_speech",
- AudioIsolation: "audio_isolation",
- DubbingRead: "dubbing_read",
- DubbingWrite: "dubbing_write",
- ProjectsRead: "projects_read",
- ProjectsWrite: "projects_write",
- AudioNativeRead: "audio_native_read",
- AudioNativeWrite: "audio_native_write",
- PronunciationDictionariesRead: "pronunciation_dictionaries_read",
- PronunciationDictionariesWrite: "pronunciation_dictionaries_write",
- VoicesRead: "voices_read",
- VoicesWrite: "voices_write",
- ModelsRead: "models_read",
- SpeechHistoryRead: "speech_history_read",
- SpeechHistoryWrite: "speech_history_write",
- UserRead: "user_read",
- WorkspaceRead: "workspace_read",
- WorkspaceWrite: "workspace_write",
- }
-
- StringToPermission = map[string]Permission{
- "text_to_speech": TextToSpeech,
- "speech_to_speech": SpeechToSpeech,
- "audio_isolation": AudioIsolation,
- "dubbing_read": DubbingRead,
- "dubbing_write": DubbingWrite,
- "projects_read": ProjectsRead,
- "projects_write": ProjectsWrite,
- "audio_native_read": AudioNativeRead,
- "audio_native_write": AudioNativeWrite,
- "pronunciation_dictionaries_read": PronunciationDictionariesRead,
- "pronunciation_dictionaries_write": PronunciationDictionariesWrite,
- "voices_read": VoicesRead,
- "voices_write": VoicesWrite,
- "models_read": ModelsRead,
- "speech_history_read": SpeechHistoryRead,
- "speech_history_write": SpeechHistoryWrite,
- "user_read": UserRead,
- "workspace_read": WorkspaceRead,
- "workspace_write": WorkspaceWrite,
- }
-
- PermissionIDs = map[Permission]int{
- TextToSpeech: 1,
- SpeechToSpeech: 2,
- AudioIsolation: 3,
- DubbingRead: 4,
- DubbingWrite: 5,
- ProjectsRead: 6,
- ProjectsWrite: 7,
- AudioNativeRead: 8,
- AudioNativeWrite: 9,
- PronunciationDictionariesRead: 10,
- PronunciationDictionariesWrite: 11,
- VoicesRead: 12,
- VoicesWrite: 13,
- ModelsRead: 14,
- SpeechHistoryRead: 15,
- SpeechHistoryWrite: 16,
- UserRead: 17,
- WorkspaceRead: 18,
- WorkspaceWrite: 19,
- }
-
- IdToPermission = map[int]Permission{
- 1: TextToSpeech,
- 2: SpeechToSpeech,
- 3: AudioIsolation,
- 4: DubbingRead,
- 5: DubbingWrite,
- 6: ProjectsRead,
- 7: ProjectsWrite,
- 8: AudioNativeRead,
- 9: AudioNativeWrite,
- 10: PronunciationDictionariesRead,
- 11: PronunciationDictionariesWrite,
- 12: VoicesRead,
- 13: VoicesWrite,
- 14: ModelsRead,
- 15: SpeechHistoryRead,
- 16: SpeechHistoryWrite,
- 17: UserRead,
- 18: WorkspaceRead,
- 19: WorkspaceWrite,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/elevenlabs/permissions.yaml b/pkg/analyzer/analyzers/elevenlabs/permissions.yaml
deleted file mode 100644
index 05977065582f..000000000000
--- a/pkg/analyzer/analyzers/elevenlabs/permissions.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-permissions:
- - text_to_speech
- - speech_to_speech
- # - sound_generation
- - audio_isolation
- # - voice_generation
- - dubbing_read
- - dubbing_write
- - projects_read
- - projects_write
- - audio_native_read
- - audio_native_write
- - pronunciation_dictionaries_read
- - pronunciation_dictionaries_write
- - voices_read
- - voices_write
- - models_read
- # - models_write
- - speech_history_read
- - speech_history_write
- - user_read
- # - user_write
- - workspace_read
- - workspace_write
diff --git a/pkg/analyzer/analyzers/elevenlabs/requests.go b/pkg/analyzer/analyzers/elevenlabs/requests.go
deleted file mode 100644
index ea4d3ec54b33..000000000000
--- a/pkg/analyzer/analyzers/elevenlabs/requests.go
+++ /dev/null
@@ -1,782 +0,0 @@
-package elevenlabs
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "mime/multipart"
- "net/http"
- "slices"
- "strings"
-)
-
-// permissionToAPIMap contain the API endpoints for each scope/permission
-// api docs: https://elevenlabs.io/docs/api-reference/introduction
-var permissionToAPIMap = map[Permission]string{
- TextToSpeech: "https://api.elevenlabs.io/v1/text-to-speech/%s", // require voice id
- SpeechToSpeech: "https://api.elevenlabs.io/v1/speech-to-speech/%s", // require voice id
- AudioIsolation: "https://api.elevenlabs.io/v1/audio-isolation",
- DubbingRead: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id
- DubbingWrite: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id
- ProjectsRead: "https://api.elevenlabs.io/v1/projects",
- ProjectsWrite: "https://api.elevenlabs.io/v1/projects/%s", // require project id
- AudioNativeWrite: "https://api.elevenlabs.io/v1/audio-native/%s/content", // require project id
- PronunciationDictionariesRead: "https://api.elevenlabs.io/v1/pronunciation-dictionaries",
- PronunciationDictionariesWrite: "https://api.elevenlabs.io/v1/pronunciation-dictionaries/%s/remove-rules", // require pronunciation dictionary id
- VoicesRead: "https://api.elevenlabs.io/v1/voices",
- VoicesWrite: "https://api.elevenlabs.io/v1/voices/%s", // require voice id
- ModelsRead: "https://api.elevenlabs.io/v1/models",
- SpeechHistoryRead: "https://api.elevenlabs.io/v1/history",
- SpeechHistoryWrite: "https://api.elevenlabs.io/v1/history/%s", // require history item id
- UserRead: "https://api.elevenlabs.io/v1/user",
- WorkspaceWrite: "https://api.elevenlabs.io/v1/workspace/invites",
-}
-
-var (
- // not exist key
- fakeID = "_thou_shalt_not_exist_"
- // error statuses
- NotVerifiable = "api_key_not_verifiable"
- InvalidAPIKey = "invalid_api_key"
- MissingPermissions = "missing_permissions"
- DubbingNotFound = "dubbing_not_found"
- ProjectNotFound = "project_not_found"
- VoiceDoesNotExist = "voice_does_not_exist"
- InvalidSubscription = "invalid_subscription"
- PronunciationDictionaryNotFound = "pronunciation_dictionary_not_found"
- InternalServerError = "internal_server_error"
- InvalidProjectID = "invalid_project_id"
- ModelNotFound = "model_not_found"
- VoiceNotFound = "voice_not_found"
- InvalidContent = "invalid_content"
-)
-
-// ErrorResponse is the error response for all APIs
-type ErrorResponse struct {
- Detail struct {
- Status string `json:"status"`
- } `json:"detail"`
-}
-
-// UserResponse is the /user API response
-type UserResponse struct {
- UserID string `json:"user_id"`
- FirstName string `json:"first_name"`
- Subscription struct {
- Tier string `json:"tier"`
- Status string `json:"status"`
- } `json:"subscription"`
-}
-
-// HistoryResponse is the /history API response
-type HistoryResponse struct {
- History []struct {
- ID string `json:"history_item_id"`
- ModelID string `json:"model_id"`
- VoiceID string `json:"voice_id"`
- } `json:"history"`
-}
-
-// VoiceResponse is the /voices API response
-type VoicesResponse struct {
- Voices []struct {
- ID string `json:"voice_id"`
- Name string `json:"name"`
- Category string `json:"category"`
- } `json:"voices"`
-}
-
-// ProjectsResponse is the /projects API response
-type ProjectsResponse struct {
- Projects []struct {
- ID string `json:"project_id"`
- Name string `json:"name"`
- State string `json:"state"`
- AccessLevel string `json:"access_level"`
- } `json:"projects"`
-}
-
-// PronunciationDictionaries is the /pronunciation-dictionaries API response
-type PronunciationDictionariesResponse struct {
- PronunciationDictionaries []struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"pronunciation_dictionaries"`
-}
-
-// Models is the /models API response
-type ModelsResponse struct {
- ID string `json:"model_id"`
- Name string `json:"name"`
-}
-
-// AgentsResponse is the /agents API response
-type AgentsResponse struct {
- Agents []struct {
- ID string `json:"agent_id"`
- Name string `json:"name"`
- AccessLevel string `json:"access_level"`
- } `json:"agents"`
-}
-
-// ConversationResponse is the /conversation API response
-type ConversationResponse struct {
- Conversations []struct {
- AgentID string `json:"agent_id"`
- ID string `json:"conversation_id"`
- Status string `json:"status"`
- }
-}
-
-// getAPIUrl return the API Url mapped to the permission
-func getAPIUrl(permission Permission) string {
- apiUrl := permissionToAPIMap[permission]
- if strings.Contains(apiUrl, "%s") {
- return fmt.Sprintf(apiUrl, fakeID)
- }
-
- return apiUrl
-}
-
-// makeElevenLabsRequest send the API request to passed url with passed key as API Key and return response body and status code
-func makeElevenLabsRequest(client *http.Client, url, method, key string) ([]byte, int, error) {
- // create request
- req, err := http.NewRequest(method, url, http.NoBody)
- if err != nil {
- return nil, 0, err
- }
-
- // add key in the header
- req.Header.Add("xi-api-key", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, 0, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- /*
- the reason to translate body to byte and does not directly return http.Response
- is if we return http.Response we cannot close the body in defer. If we do we will get an error
- when reading body outside this function
- */
- responseBodyByte, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, 0, err
- }
-
- return responseBodyByte, resp.StatusCode, nil
-}
-
-// makeElevenLabsRequestWithPayload sends a POST/PATCH API request to the passed URL with the given key as the API Key
-// and an optional payload. It returns the response body and status code.
-func makeElevenLabsRequestWithPayload(client *http.Client, url, method, contentType, key string, payload []byte) ([]byte, int, error) {
- // Create request with payload
- req, err := http.NewRequest(method, url, bytes.NewBuffer(payload))
- if err != nil {
- return nil, 0, err
- }
-
- // Add headers
- req.Header.Add("xi-api-key", key)
- req.Header.Add("Content-Type", contentType)
-
- // Send the request
- resp, err := client.Do(req)
- if err != nil {
- return nil, 0, err
- }
-
- // ensure the response body is properly closed
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- // read the response body
- responseBodyByte, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, 0, err
- }
-
- return responseBodyByte, resp.StatusCode, nil
-}
-
-// getHistory get history item using the key passed and add them to secret info
-func getHistory(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryRead), http.MethodGet, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var history HistoryResponse
-
- if err := json.Unmarshal(response, &history); err != nil {
- return err
- }
-
- // add history read scope to secret info
- secretInfo.AppendPermission(PermissionStrings[SpeechHistoryRead])
- // map resource to secret info
- for _, historyItem := range history.History {
- secretInfo.AppendResource(ElevenLabsResource{
- ID: historyItem.ID,
- Name: "", // no name
- Type: "History",
- Permission: PermissionStrings[SpeechHistoryRead],
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking history read scope", statusCode)
- }
-}
-
-// deleteHistory try to delete a history item. The item must not exist.
-func deleteHistory(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryWrite), http.MethodDelete, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusInternalServerError:
- // for some reason if we send fake id and token has the permission, the history api return 500 error instead of 404
- // issue opened in elevenlabs-docs: https://github.com/elevenlabs/elevenlabs-docs/issues/649
- return handleErrorStatus(response, PermissionStrings[SpeechHistoryWrite], secretInfo, InternalServerError)
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking history write scope", statusCode)
- }
-}
-
-// deleteDubbing try to delete a dubbing. The item must not exist.
-func deleteDubbing(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingWrite), http.MethodDelete, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusNotFound:
- // as we send fake id, if permission is assigned to token we must get 404 dubbing not found
- if err := handleErrorStatus(response, PermissionStrings[DubbingWrite], secretInfo, DubbingNotFound); err != nil {
- return err
- }
-
- // add read scope of dubbing to avoid get dubbing api call
- secretInfo.AppendPermission(PermissionStrings[DubbingRead])
-
- return nil
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking dubbing write scope", statusCode)
- }
-}
-
-// getDebugging try to get a dubbing. The item must not exist.
-func getDebugging(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingRead), http.MethodGet, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusNotFound:
- // as we send fake id, if permission is assigned to token we must get 404 dubbing not found
- return handleErrorStatus(response, PermissionStrings[DubbingRead], secretInfo, DubbingNotFound)
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking dubbing read scope", statusCode)
- }
-}
-
-// getVoices get list of voices using the key passed and add them to secret info
-func getVoices(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(VoicesRead), http.MethodGet, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var voices VoicesResponse
-
- if err := json.Unmarshal(response, &voices); err != nil {
- return err
- }
-
- // add voices read scope to secret info
- secretInfo.AppendPermission(PermissionStrings[VoicesRead])
- // map resource to secret info
- for _, voice := range voices.Voices {
- secretInfo.AppendResource(ElevenLabsResource{
- ID: voice.ID,
- Name: voice.Name,
- Type: "Voice",
- Permission: PermissionStrings[VoicesRead],
- Metadata: map[string]string{
- "category": voice.Category,
- },
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking voice read scope", statusCode)
- }
-}
-
-// deleteVoice try to delete a voice. The item must not exist.
-func deleteVoice(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(VoicesWrite), http.MethodDelete, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusBadRequest:
- // if permission was assigned to scope we should get 400 error with voice not found status
- return handleErrorStatus(response, PermissionStrings[VoicesWrite], secretInfo, VoiceDoesNotExist)
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking voice write scope", statusCode)
- }
-}
-
-// getProjects get list of projects using the key passed and add them to secret info
-func getProjects(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ProjectsRead), http.MethodGet, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var projects ProjectsResponse
-
- if err := json.Unmarshal(response, &projects); err != nil {
- return err
- }
-
- // add project read scope to secret info
- secretInfo.AppendPermission(PermissionStrings[ProjectsRead])
- // map resource to secret info
- for _, project := range projects.Projects {
- secretInfo.AppendResource(ElevenLabsResource{
- ID: project.ID,
- Name: project.Name,
- Type: "Project",
- Permission: PermissionStrings[ProjectsRead],
- Metadata: map[string]string{
- "state": project.State,
- "access level": project.AccessLevel, // access level of project
- },
- })
- }
-
- return nil
- case http.StatusForbidden:
- // if token has the permission but trail is free, projects are not accessible
- return handleErrorStatus(response, PermissionStrings[ProjectsRead], secretInfo, InvalidSubscription)
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking projects read scope", statusCode)
- }
-}
-
-// deleteProject try to delete a project. The item must not exist.
-func deleteProject(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ProjectsWrite), http.MethodDelete, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusBadRequest:
- // if permission was assigned to token we should get 400 error with project not found status
- return handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, ProjectNotFound)
- case http.StatusForbidden:
- // if token has the permission but trail is free, projects are not accessible
- return handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, InvalidSubscription)
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking project write scope", statusCode)
- }
-}
-
-// getPronunciationDictionaries get list of pronunciation dictionaries using the key passed and add them to secret info
-func getPronunciationDictionaries(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(PronunciationDictionariesRead), http.MethodGet, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var PDs PronunciationDictionariesResponse
-
- if err := json.Unmarshal(response, &PDs); err != nil {
- return err
- }
-
- // add voices read scope to secret info
- secretInfo.AppendPermission(PermissionStrings[PronunciationDictionariesRead])
- // map resource to secret info
- for _, pd := range PDs.PronunciationDictionaries {
- secretInfo.AppendResource(ElevenLabsResource{
- ID: pd.ID,
- Name: pd.Name,
- Type: "Pronunciation Dictionary",
- Permission: PermissionStrings[PronunciationDictionariesRead],
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking pronunciation dictionaries read scope", statusCode)
- }
-}
-
-// removePronunciationDictionariesRule try to remove a rule from pronunciation dictionaries. The item must not exist.
-func removePronunciationDictionariesRule(client *http.Client, key string, secretInfo *SecretInfo) error {
- // send empty list of rule strings
- payload := map[string]interface{}{
- "rule_strings": []string{""},
- }
-
- payloadBytes, _ := json.Marshal(payload)
- response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(PronunciationDictionariesWrite), http.MethodPost,
- "application/json", key, payloadBytes)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusNotFound:
- // if permission was assigned to token we should get 404 error with pronunciation_dictionary_not_found status
- return handleErrorStatus(response, PermissionStrings[PronunciationDictionariesWrite], secretInfo, PronunciationDictionaryNotFound)
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking pronunciation dictionary write scope", statusCode)
- }
-}
-
-// getModels list models using the key passed and add them to secret info
-func getModels(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ModelsRead), http.MethodGet, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var models []ModelsResponse
-
- if err := json.Unmarshal(response, &models); err != nil {
- return err
- }
-
- // add models read scope to secret info
- secretInfo.AppendPermission(PermissionStrings[ModelsRead])
- // map resource to secret info
- for _, model := range models {
- secretInfo.AppendResource(ElevenLabsResource{
- ID: model.ID,
- Name: model.Name,
- Type: "Model",
- Permission: PermissionStrings[ModelsRead],
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode)
- }
-}
-
-// updateAudioNativeProject try to update a project content. The item must not exist.
-func updateAudioNativeProject(client *http.Client, key string, secretInfo *SecretInfo) error {
- // create a buffer to hold the multipart form data
- body := &bytes.Buffer{}
- writer := multipart.NewWriter(body)
-
- // add required fields to multipart form body
- _ = writer.WriteField("auto_convert", "false")
- _ = writer.WriteField("auto_publish", "false")
- // close the writer
- _ = writer.Close()
-
- response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioNativeWrite), http.MethodPost,
- writer.FormDataContentType(), key, body.Bytes())
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusBadRequest:
- // if the permission is assigned to token, the api should return 400 with invalid project id
- if err := handleErrorStatus(response, PermissionStrings[AudioNativeWrite], secretInfo, InvalidProjectID); err != nil {
- return err
- }
-
- // add read permission as no separate API exist to check read audio native permission
- secretInfo.AppendPermission(PermissionStrings[AudioNativeRead])
- return nil
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking audio native write scope", statusCode)
- }
-}
-
-// deleteInviteFromWorkspace try to remove a invite from workspace. The item must not exist.
-func deleteInviteFromWorkspace(client *http.Client, key string, secretInfo *SecretInfo) error {
- // send fake email in payload
- payload := map[string]interface{}{
- "email": fakeID + "@example.com",
- }
-
- payloadBytes, _ := json.Marshal(payload)
- response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(WorkspaceWrite), http.MethodDelete,
- "application/json", key, payloadBytes)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusInternalServerError:
- // for some reason if we send fake email and token has the permission, the workspace invite api return 500 error instead of 404
- if err := handleErrorStatus(response, PermissionStrings[WorkspaceWrite], secretInfo, InternalServerError); err != nil {
- return err
- }
-
- // add read permission as no separate API exist to check workspace read permission
- secretInfo.AppendPermission(PermissionStrings[WorkspaceRead])
- return nil
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking workspace write scope", statusCode)
- }
-}
-
-// textToSpeech try to convert text to speech. The model id and voice id is fake so it actually never happens.
-func textToSpeech(client *http.Client, key string, secretInfo *SecretInfo) error {
- // send fake model id in payload
- payload := map[string]interface{}{
- "text": "This is trufflehog trying to check text to speech permission of the token",
- "model_id": fakeID,
- }
-
- payloadBytes, _ := json.Marshal(payload)
- response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(TextToSpeech), http.MethodPost,
- "application/json", key, payloadBytes)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusBadRequest:
- // if permission is assigned to token, error status will be either model not found or voice not found as we sent both fake ;)
- return handleErrorStatus(response, PermissionStrings[TextToSpeech], secretInfo, ModelNotFound, VoiceNotFound)
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking text to speech scope", statusCode)
- }
-}
-
-// speechToSpeech try to change a voice in speech. The model id and voice id is fake so it actually never happens.
-func speechToSpeech(client *http.Client, key string, secretInfo *SecretInfo) error {
- // create a buffer to hold the multipart form data
- body := &bytes.Buffer{}
- writer := multipart.NewWriter(body)
-
- // add required fields to multipart form body
- _ = writer.WriteField("model_id", fakeID)
- _ = writer.WriteField("seed", "1")
- _ = writer.WriteField("remove_background_noise", "false")
- audio, _ := writer.CreateFormFile("audio", "")
- _, _ = audio.Write([]byte("This is example fake audio for api call"))
- // close the writer
- _ = writer.Close()
-
- response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(SpeechToSpeech), http.MethodPost,
- writer.FormDataContentType(), key, body.Bytes())
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusBadRequest:
- return handleErrorStatus(response, PermissionStrings[SpeechToSpeech], secretInfo, InvalidContent)
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking speech to speech scope", statusCode)
- }
-}
-
-// audioIsolation try to remove background noise from a voice. The file will be corrupted so it should return an error.
-func audioIsolation(client *http.Client, key string, secretInfo *SecretInfo) error {
- // create a buffer to hold the multipart form data
- body := &bytes.Buffer{}
- writer := multipart.NewWriter(body)
-
- audio, _ := writer.CreateFormFile("audio", "")
- _, _ = audio.Write([]byte("This is example fake audio for api call"))
- // close the writer
- _ = writer.Close()
-
- response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioIsolation), http.MethodPost,
- writer.FormDataContentType(), key, body.Bytes())
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusBadRequest:
- return handleErrorStatus(response, PermissionStrings[AudioIsolation], secretInfo, InvalidContent)
- case http.StatusUnauthorized:
- return handleErrorStatus(response, "", secretInfo, MissingPermissions)
- default:
- return fmt.Errorf("unexpected status code: %d while checking audio isolation speech scope", statusCode)
- }
-}
-
-/*
-getAgents get all user agents which are not bound with any permission
-call APIs in pattern: agents->conversation
-*/
-func getAgents(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeElevenLabsRequest(client, "https://api.elevenlabs.io/v1/convai/agents", http.MethodGet, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var agents AgentsResponse
-
- if err := json.Unmarshal(response, &agents); err != nil {
- return err
- }
-
- // map resource to secret info
- for _, agent := range agents.Agents {
- resource := ElevenLabsResource{
- ID: agent.ID,
- Name: agent.Name,
- Type: "Agent",
- Permission: "", // not binded with any permission
- Metadata: map[string]string{
- "access level": agent.AccessLevel,
- },
- }
- secretInfo.AppendResource(resource)
- // get agent conversations
- if err := getConversation(client, key, agent.ID, secretInfo); err != nil {
- return err
- }
- }
-
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode)
- }
-}
-
-// getConversation list all agent conversations using the key and agentID passed and add them to secret info
-func getConversation(client *http.Client, key, agentID string, secretInfo *SecretInfo) error {
- apiUrl := fmt.Sprintf("https://api.elevenlabs.io/v1/convai/conversations?agent_id=%s", agentID)
- response, statusCode, err := makeElevenLabsRequest(client, apiUrl, http.MethodGet, key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var conversations ConversationResponse
-
- if err := json.Unmarshal(response, &conversations); err != nil {
- return err
- }
-
- // map resource to secret info
- for _, conversation := range conversations.Conversations {
- secretInfo.AppendResource(ElevenLabsResource{
- ID: conversation.ID,
- Name: "", // no name
- Type: "Conversation",
- Permission: "", // not binded with any permission
- Metadata: map[string]string{
- "status": conversation.Status,
- },
- })
- }
-
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode)
- }
-}
-
-// handleErrorStatus handle error response, check if expected error status is in the response and add require permission to secret info
-// this is used in case where we expect error response with specific status mostly in write calls
-func handleErrorStatus(response []byte, permissionToAdd string, secretInfo *SecretInfo, expectedErrStatuses ...string) error {
- // check if status in response is what is expected to be
- ok, err := checkErrorStatus(response, expectedErrStatuses...)
- if err != nil {
- return err
- }
-
- // if permission to add was passed and it was expected error status add the permission
- if permissionToAdd != "" && ok {
- secretInfo.AppendPermission(permissionToAdd)
- } else if permissionToAdd != "" && !ok {
- // if permission to add was passed and it was unexpected error status - return error
- return errors.New("unexpected error response")
- }
-
- return nil
-}
-
-// checkErrorStatus check if any of expected error status exist in actual API error response
-func checkErrorStatus(response []byte, expectedStatuses ...string) (bool, error) {
- var errorResp ErrorResponse
-
- if err := json.Unmarshal(response, &errorResp); err != nil {
- return false, err
- }
-
- if slices.Contains(expectedStatuses, errorResp.Detail.Status) {
- return true, nil
- }
-
- return false, nil
-}
diff --git a/pkg/analyzer/analyzers/elevenlabs/result_output.json b/pkg/analyzer/analyzers/elevenlabs/result_output.json
deleted file mode 100644
index aeef5b7633bf..000000000000
--- a/pkg/analyzer/analyzers/elevenlabs/result_output.json
+++ /dev/null
@@ -1,439 +0,0 @@
-{
- "AnalyzerType": 6,
- "Bindings": [
- {
- "Resource": {
- "Name": "Ahmed",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/User/b9Rou9mHDmTYd8cdWkg2Yk4P2lq1",
- "Type": "User",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "user_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Alice",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/Xb7hH8MSUJpSbSDYk0k2",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Aria",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/9BWtsMINqrJLrRacOk9x",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Bill",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/pqHfZKP75CvOlQylNhV4",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Brian",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/nPczCjzI2devNBz1zQrb",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Callum",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/N2lVS1w4EtoT3dr4eOWO",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Charlie",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/IKne3meq5aSn9XLyUdCD",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Charlotte",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/XB0fDUnXU5powFXDhCwa",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Chris",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/iP95p4xoKVk53GoZ742B",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Daniel",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/onwK4e9ZLuTAKqWW03F9",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Eleven English v1",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_monolingual_v1",
- "Type": "Model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "models_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Eleven English v2",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_english_sts_v2",
- "Type": "Model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "models_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Eleven Flash v2",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_flash_v2",
- "Type": "Model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "models_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Eleven Flash v2.5",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_flash_v2_5",
- "Type": "Model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "models_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Eleven Multilingual v1",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_v1",
- "Type": "Model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "models_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Eleven Multilingual v2",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_v2",
- "Type": "Model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "models_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Eleven Multilingual v2",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_sts_v2",
- "Type": "Model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "models_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Eleven Turbo v2",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_turbo_v2",
- "Type": "Model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "models_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Eleven Turbo v2.5",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_turbo_v2_5",
- "Type": "Model",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "models_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Eric",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/cjVigY5qzO86Huf0OWal",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "George",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/JBFqnCBsd6RMkjVDRZzb",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Jessica",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/cgSgspJ2msm6clMCkdW9",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Laura",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/FGY2WhTYpPnrIDTdsKH5",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Liam",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/TX3LPaxmHKxFdv7VOQHJ",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Lily",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/pFZP5JQG7iQjIQuC4Bku",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Matilda",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/XrExE9yKIg1WjnnlVkGX",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "River",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/SAz9YHcvj6GT2YYXdXww",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Roger",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/CwhRBWXzGAHq8TQ4Fs17",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sarah",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/EXAVITQu4vr4xnSDxMaL",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Will",
- "FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/bIHbv24MWmeRgasZH58o",
- "Type": "Voice",
- "Metadata": {
- "category": "premade"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "voices_read",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {
- "Valid_Key": true
- }
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/fastly/fastly.go b/pkg/analyzer/analyzers/fastly/fastly.go
deleted file mode 100644
index 40dfe94dc033..000000000000
--- a/pkg/analyzer/analyzers/fastly/fastly.go
+++ /dev/null
@@ -1,185 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go fastly
-package fastly
-
-import (
- "fmt"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeFastly
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, exist := credInfo["key"]
- if !exist {
- return nil, fmt.Errorf("key not found in credential info")
- }
-
- // analyze permissions
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
-
- // secret info to analyzer
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- // just print the error in cli and continue as a partial success
- color.Red("[x] Error : %s", err.Error())
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- color.Green("[!] Valid Fastly API key\n\n")
-
- if info.TokenInfo.hasGlobalScope() {
- printUserInfo(info.UserInfo)
- }
-
- printScopes(info.TokenInfo.Scopes)
-
- if len(info.Resources) > 0 {
- printResources(info.Resources)
- }
-
- color.Yellow("\n[i] Expires: %s", info.TokenInfo.ExpiresAt)
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- // create http client
- client := analyzers.NewAnalyzeClient(cfg)
-
- var secretInfo = &SecretInfo{}
-
- // capture the token details
- if err := captureTokenInfo(client, key, secretInfo); err != nil {
- return nil, err
- }
-
- /*
- Fastly defines four types of permissions. Two of these are related specifically to purging:
-
- - If a token has either `purge_select` or `purge_all` access, it is limited to calling purge-related APIs only.
- - If a token has `global` or `global:read` access, it can call APIs that retrieve resource and user information.
- */
-
- if !secretInfo.TokenInfo.hasGlobalScope() {
- return secretInfo, nil
- }
-
- // capture the user information
- if err := captureUserInfo(client, key, secretInfo); err != nil {
- return nil, err
- }
-
- // capture the resources
- if err := captureResources(client, key, secretInfo); err != nil {
- // return secretInfo as well in case of error for partial success
- return secretInfo, err
- }
-
- return secretInfo, nil
-}
-
-// secretInfoToAnalyzerResult translate secret info to Analyzer Result
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeFastly,
- Metadata: map[string]any{},
- Bindings: make([]analyzers.Binding, 0),
- }
-
- // extract information from resource to create bindings and append to result bindings
- for _, resource := range info.Resources {
- binding := analyzers.Binding{
- Resource: *secretInfoResourceToAnalyzerResource(resource),
- Permission: analyzers.Permission{
- Value: info.TokenInfo.Scope,
- },
- }
-
- if resource.Parent != nil {
- binding.Resource.Parent = secretInfoResourceToAnalyzerResource(*resource.Parent)
- }
-
- result.Bindings = append(result.Bindings, binding)
-
- }
-
- return &result
-}
-
-// secretInfoResourceToAnalyzerResource translate secret info resource to analyzer resource for binding
-func secretInfoResourceToAnalyzerResource(resource FastlyResource) *analyzers.Resource {
- analyzerRes := analyzers.Resource{
- // make fully qualified name unique
- FullyQualifiedName: resource.Type + "/" + resource.ID,
- Name: resource.Name,
- Type: resource.Type,
- Metadata: map[string]any{},
- }
-
- for key, value := range resource.Metadata {
- analyzerRes.Metadata[key] = value
- }
-
- return &analyzerRes
-}
-
-// cli print functions
-func printUserInfo(user User) {
- color.Yellow("[i] User Information:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"ID", "Name", "Login", "Role", "Last Active At"})
- t.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Name), color.GreenString(user.Login), color.GreenString(user.Role), color.GreenString(user.LastActiveAt)})
-
- t.Render()
-}
-
-func printScopes(scopes []string) {
- color.Yellow("[i] Scopes:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Scopes"})
- for _, scope := range scopes {
- t.AppendRow(table.Row{color.GreenString(scope)})
- }
- t.Render()
-}
-
-func printResources(resources []FastlyResource) {
- color.Yellow("[i] Resources:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Name", "Type"})
- for _, resource := range resources {
- t.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})
- }
-
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/fastly/fastly_test.go b/pkg/analyzer/analyzers/fastly/fastly_test.go
deleted file mode 100644
index 022f23a61cf3..000000000000
--- a/pkg/analyzer/analyzers/fastly/fastly_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package fastly
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed result_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- key := testSecrets.MustGetField("FASTLYPERSONALTOKEN_TOKEN")
-
- tests := []struct {
- name string
- key string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid fastly token",
- key: key,
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.FullyQualifiedName == bindings[j].Resource.FullyQualifiedName {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.FullyQualifiedName < bindings[j].Resource.FullyQualifiedName
- })
-}
diff --git a/pkg/analyzer/analyzers/fastly/models.go b/pkg/analyzer/analyzers/fastly/models.go
deleted file mode 100644
index 8432295d82c6..000000000000
--- a/pkg/analyzer/analyzers/fastly/models.go
+++ /dev/null
@@ -1,219 +0,0 @@
-package fastly
-
-import "sync"
-
-const (
- // types
- TypeUserToken string = "User Token"
- TypeAutomationToken string = "Automation Token"
- TypeService string = "Service"
- TypeSvcVersion string = "Service Version"
- TypeSvcVersionACL string = "Service Version ACL"
- TypeSvcVersionDict string = "Service Version Dictionary"
- TypeSvcVersionBackend string = "Service Version Backend"
- TypeSvcVersionDomain string = "Service Version Domain"
- TypeSvcVersionHealthCheck string = "Service Version Health Check"
- TypeConfigStore string = "Config Store"
- TypeSecretStore string = "Secret Store"
- TypeTLSPrivateKey string = "TLS Private Key"
- TypeTLSCertificate string = "TLS Certificates"
- TypeTLSDomain string = "TLS Domain"
- TypeInvoice string = "Invoice"
-)
-
-type SecretInfo struct {
- mu sync.RWMutex
-
- UserInfo User
- TokenInfo SelfToken
- Resources []FastlyResource
-}
-
-type FastlyResource struct {
- ID string
- Name string
- Type string
- Metadata map[string]string
- Parent *FastlyResource
-}
-
-// AppendResource append resource to secret info resource list
-func (s *SecretInfo) appendResource(resource FastlyResource) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- s.Resources = append(s.Resources, resource)
-}
-
-// listResourceByType returns a list of resources matching the given type.
-func (s *SecretInfo) listResourceByType(resourceType string) []FastlyResource {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- resources := make([]FastlyResource, 0, len(s.Resources))
- for _, resource := range s.Resources {
- if resource.Type == resourceType {
- resources = append(resources, resource)
- }
- }
-
- return resources
-}
-
-// API Response models
-
-// User is /current_user API Response
-type User struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Login string `json:"login"`
- Role string `json:"role"`
- LastActiveAt string `json:"last_active_at"`
-}
-
-// SelfToken is /tokens/self API Response
-type SelfToken struct {
- ID string `json:"id"`
- UserID string `json:"user_id"`
- Name string `json:"name"`
- LastUsedAt string `json:"last_used_at"`
- ExpiresAt string `json:"expires_at"`
- Scope string `json:"scope"`
- Scopes []string `json:"scopes"`
- Services []string `json:"services"`
-}
-
-// hasGlobalScope returns true if any global scope is assigned to the token
-func (t SelfToken) hasGlobalScope() bool {
- for _, scope := range t.Scopes {
- if scope == PermissionStrings[Global] || scope == PermissionStrings[GlobalRead] {
- return true
- }
- }
-
- return false
-}
-
-// TokenData is /automation-tokens API Response
-type TokenData struct {
- Data []Token `json:"data"`
-}
-
-// Token is /tokens API Response
-type Token struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Scope string `json:"scope"`
- Role string `json:"role"`
- ExpiresAt string `json:"expires_at"`
-}
-
-// Service is /service API Response
-type Service struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"`
-}
-
-// Version is /service//version API Response
-type Version struct {
- Number int `json:"number"`
- Active bool `json:"active"`
- Deployed bool `json:"deployed"`
- ServiceID string `json:"service_id"`
-}
-
-// ACL is /service//version//acl API Response
-type ACL struct {
- ID string `json:"id"`
- Name string `json:"name"`
-}
-
-// Dictionary is the /service//version//dictionary API Response
-type Dictionary struct {
- ID string `json:"id"`
- Name string `json:"name"`
-}
-
-// Backend is the /service//version//backend API Response
-type Backend struct {
- Name string `json:"name"`
- Address string `json:"address"`
- Port string `json:"port"`
-}
-
-// Domain is the /service//version//domain API Response
-type Domain struct {
- Name string `json:"name"`
-}
-
-// HealthCheck is the /service//version//healthcheck API Response
-type HealthCheck struct {
- Name string `json:"name"`
- Host string `json:"host"`
- Path string `json:"path"`
- Method string `json:"method"`
-}
-
-// ConfigStore is the /resources/stores/config API Response
-type ConfigStore struct {
- ID string `json:"id"`
- Name string `json:"name"`
-}
-
-// SecretStoreData is the /resources/stores/secret API Response
-type SecretStoreData struct {
- Data []SecretStore `json:"data"`
-}
-
-// SecretStore is a single store in SecretStoreData
-type SecretStore struct {
- ID string `json:"id"`
- Name string `json:"name"`
-}
-
-// TLSPrivateKeyData is the /tls/private_keys API Response
-type TLSPrivateKeyData struct {
- Data []TLSPrivateKey `json:"data"`
-}
-
-// TLSPrivateKey is the single TLS private key in TLSPrivateKeyData
-type TLSPrivateKey struct {
- ID string `json:"id"`
- Name string `json:"name"`
-}
-
-// TLSCertificatesData is the /tls/certificates API Response
-type TLSCertificatesData struct {
- Data []TLSCertificate `json:"data"`
-}
-
-// TLSCertificate is the single TLS certificate in TLSCertificatesData
-type TLSCertificate struct {
- ID string `json:"id"`
- Name string `json:"name"`
-}
-
-// TLSDomainsData is the /tls/domains API Response
-type TLSDomainsData struct {
- Data []TLSDomain `json:"data"`
-}
-
-// TLSDomain is the single TLS Domain in TLSDomainsData
-type TLSDomain struct {
- ID string `json:"id"`
-}
-
-// InvoicesData is the /billing/v3/invoices API Response
-type InvoicesData struct {
- Data []Invoice `json:"data"`
-}
-
-// Invoice is the single invoice in InvoicesData
-type Invoice struct {
- ID string `json:"invoice_id"`
- CustomerID string `json:"customer_id"`
- Region string `json:"region"`
- StatementNo string `json:"statement_number"`
- InvoicePostedOn string `json:"invoice_posted_on"`
-}
diff --git a/pkg/analyzer/analyzers/fastly/permissions.go b/pkg/analyzer/analyzers/fastly/permissions.go
deleted file mode 100644
index 58cbe7cdfaa3..000000000000
--- a/pkg/analyzer/analyzers/fastly/permissions.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package fastly
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Global Permission = iota
- GlobalRead Permission = iota
- PurgeAll Permission = iota
- PurgeSelect Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Global: "global",
- GlobalRead: "global:read",
- PurgeAll: "purge_all",
- PurgeSelect: "purge_select",
- }
-
- StringToPermission = map[string]Permission{
- "global": Global,
- "global:read": GlobalRead,
- "purge_all": PurgeAll,
- "purge_select": PurgeSelect,
- }
-
- PermissionIDs = map[Permission]int{
- Global: 1,
- GlobalRead: 2,
- PurgeAll: 3,
- PurgeSelect: 4,
- }
-
- IdToPermission = map[int]Permission{
- 1: Global,
- 2: GlobalRead,
- 3: PurgeAll,
- 4: PurgeSelect,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/fastly/permissions.yaml b/pkg/analyzer/analyzers/fastly/permissions.yaml
deleted file mode 100644
index 749de94bb88b..000000000000
--- a/pkg/analyzer/analyzers/fastly/permissions.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-permissions:
- - global
- - global:read
- - purge_all
- - purge_select
diff --git a/pkg/analyzer/analyzers/fastly/requests.go b/pkg/analyzer/analyzers/fastly/requests.go
deleted file mode 100644
index f7f29f1b8c0f..000000000000
--- a/pkg/analyzer/analyzers/fastly/requests.go
+++ /dev/null
@@ -1,744 +0,0 @@
-package fastly
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strconv"
- "sync"
-)
-
-type endpoint int
-
-const (
- // list of endpoints
- selfToken endpoint = iota
- currentUser
- userTokens
- automationTokens
- service
- serviceVersions
- serviceVersionACLs
- serviceVersionDictionaries
- serviceVersionBackends
- serviceVersionDomains
- serviceVersionHealthChecks
- configStores
- secretStores
- tlsPrivateKeys
- tlsCertificates
- tlsDomains
- invoices
-)
-
-var (
- baseURL = "https://api.fastly.com"
-
- // endpoints contain Fastly API endpoints
- endpoints = map[endpoint]string{
- selfToken: "/tokens/self",
- currentUser: "/current_user",
- userTokens: "/tokens",
- automationTokens: "/automation-tokens",
- service: "/service",
- serviceVersions: "/service/%s/version", // require service id
- serviceVersionACLs: "/service/%s/version/%s/acl", // require service id and version number
- serviceVersionDictionaries: "/service/%s/version/%s/dictionary", // require service id and version number
- serviceVersionBackends: "/service/%s/version/%s/backend", // require service id and version number
- serviceVersionDomains: "/service/%s/version/%s/domain", // require service id and version number
- serviceVersionHealthChecks: "/service/%s/version/%s/healthcheck", // require service id and version number
- configStores: "/resources/stores/config",
- secretStores: "/resources/stores/secret",
- tlsPrivateKeys: "/tls/private_keys",
- tlsCertificates: "/tls/certificates",
- tlsDomains: "/tls/domains",
- invoices: "/billing/v3/invoices",
-
- /*
- API:
- - /service/service_id/version/version_id/package (The use of this API is discouraged as per documentation due to limited availability release)
- - /tls/bulk/certificates (The use of this API is discouraged as per documentation due to limited availability release)
- - /security/workspaces (This Fastly Security API is only available to customers with access to the Next-Gen WAF product )
- - /events (This API just returns the account events like user logged in or user logged out etc)
-
- Utilities API Docs:
- Some of these APIs are deprecated while others return same response for everyone with a global access key.
- - https://www.fastly.com/documentation/reference/api/utils/
- */
- }
-)
-
-// makeFastlyRequest send the API request to passed url with passed key as API Key and return response body and status code
-func makeFastlyRequest(client *http.Client, endpoint, key string) ([]byte, int, error) {
- // create request
- req, err := http.NewRequest(http.MethodGet, baseURL+endpoint, http.NoBody)
- if err != nil {
- return nil, 0, err
- }
-
- // add key in the header
- req.Header.Add("Fastly-Key", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, 0, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- responseBodyByte, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, 0, err
- }
-
- return responseBodyByte, resp.StatusCode, nil
-}
-
-// captureResources try to capture all the resource that the key can access
-func captureResources(client *http.Client, key string, secretInfo *SecretInfo) error {
- var (
- wg sync.WaitGroup
- errAggWg sync.WaitGroup
- aggregatedErrs = make([]error, 0)
- errChan = make(chan error, 1)
- )
-
- errAggWg.Add(1)
- go func() {
- defer errAggWg.Done()
- for err := range errChan {
- aggregatedErrs = append(aggregatedErrs, err)
- }
- }()
-
- // helper to launch tasks concurrently.
- launchTask := func(task func() error) {
- wg.Add(1)
- go func() {
- defer wg.Done()
- if err := task(); err != nil {
- errChan <- err
- }
- }()
- }
-
- launchTask(func() error { return captureAutomationTokens(client, key, secretInfo) })
- launchTask(func() error { return captureUserTokens(client, key, secretInfo) })
-
- // capture services and their sub resources
- launchTask(func() error {
- if err := captureServices(client, key, secretInfo); err != nil {
- return err
- }
-
- services := secretInfo.listResourceByType(TypeService)
- for _, service := range services {
- if err := captureSvcVersions(client, key, service, secretInfo); err != nil {
- return err
- }
- }
-
- // capture each version sub resources
- versions := secretInfo.listResourceByType(TypeSvcVersion)
- for _, version := range versions {
- launchTask(func() error { return captureSvcVersionACLs(client, key, version, secretInfo) })
- launchTask(func() error { return captureSvcVersionDicts(client, key, version, secretInfo) })
- launchTask(func() error { return captureSvcVersionBackends(client, key, version, secretInfo) })
- launchTask(func() error { return captureSvcVersionDomains(client, key, version, secretInfo) })
- launchTask(func() error { return captureSvcVersionHealthChecks(client, key, version, secretInfo) })
- }
-
- return nil
- })
-
- launchTask(func() error { return captureConfigStores(client, key, secretInfo) })
- launchTask(func() error { return captureSecretStores(client, key, secretInfo) })
- launchTask(func() error { return capturePrivateKeys(client, key, secretInfo) })
- launchTask(func() error { return captureCertificates(client, key, secretInfo) })
- launchTask(func() error { return captureTLSDomains(client, key, secretInfo) })
- launchTask(func() error { return captureInvoices(client, key, secretInfo) })
-
- wg.Wait()
- close(errChan)
- errAggWg.Wait()
-
- if len(aggregatedErrs) > 0 {
- return errors.Join(aggregatedErrs...)
- }
-
- return nil
-}
-
-// captureTokenInfo calls `/tokens/self` API and capture the token information in secretInfo
-func captureTokenInfo(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, endpoints[selfToken], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var token SelfToken
-
- if err := json.Unmarshal(respBody, &token); err != nil {
- return err
- }
-
- if token.ExpiresAt == "" {
- token.ExpiresAt = "never"
- }
-
- secretInfo.TokenInfo = token
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired api key")
- default:
- return fmt.Errorf("unexpected status code: %d for API: %s", statusCode, endpoints[selfToken])
- }
-}
-
-// captureUserInfo calls `/current_user` API and capture the current user information in secretInfo
-func captureUserInfo(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, endpoints[currentUser], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var user User
-
- if err := json.Unmarshal(respBody, &user); err != nil {
- return err
- }
-
- secretInfo.UserInfo = user
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d for API: %s", statusCode, endpoints[currentUser])
- }
-}
-
-// captureUserTokens calls `/tokens` API
-func captureUserTokens(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, endpoints[userTokens], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var tokens []Token
-
- if err := json.Unmarshal(respBody, &tokens); err != nil {
- return err
- }
-
- for _, token := range tokens {
- resource := FastlyResource{
- ID: token.ID,
- Name: token.Name,
- Type: TypeUserToken,
- Metadata: map[string]string{
- "Scope": token.Scope,
- "Role": token.Role,
- "Expires At": token.ExpiresAt,
- },
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureAutomationTokens calls `/automation-tokens` API
-func captureAutomationTokens(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, endpoints[automationTokens], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var tokens TokenData
-
- if err := json.Unmarshal(respBody, &tokens); err != nil {
- return err
- }
-
- for _, token := range tokens.Data {
- resource := FastlyResource{
- ID: token.ID,
- Name: token.Name,
- Type: TypeAutomationToken,
- Metadata: map[string]string{
- "Scope": token.Scope,
- "Role": token.Role,
- "Expires At": token.ExpiresAt,
- },
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureServices calls `/service` API
-func captureServices(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, endpoints[service], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var services []Service
-
- if err := json.Unmarshal(respBody, &services); err != nil {
- return err
- }
-
- for _, service := range services {
- resource := FastlyResource{
- ID: service.ID,
- Name: service.Name,
- Type: TypeService,
- Metadata: map[string]string{
- "Service Type": service.Type,
- },
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d for API: %s", statusCode, endpoints[service])
- }
-}
-
-// captureSvcVersions calls `/service//version` API
-func captureSvcVersions(client *http.Client, key string, parentService FastlyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersions], parentService.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var versions []Version
-
- if err := json.Unmarshal(respBody, &versions); err != nil {
- return err
- }
-
- for _, version := range versions {
- resource := FastlyResource{
- ID: strconv.Itoa(version.Number),
- Name: parentService.ID + "/version/" + strconv.Itoa(version.Number), // versions has no specific name
- Type: TypeSvcVersion,
- Metadata: map[string]string{"service_id": version.ServiceID},
- Parent: &parentService,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureSvcVersionACLs calls `/service//version//acl` API
-func captureSvcVersionACLs(client *http.Client, key string, parentVersion FastlyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersionACLs], parentVersion.Metadata["service_id"], parentVersion.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var acls []ACL
-
- if err := json.Unmarshal(respBody, &acls); err != nil {
- return err
- }
-
- for _, acl := range acls {
- resource := FastlyResource{
- ID: acl.ID,
- Name: acl.Name,
- Type: TypeSvcVersionACL,
- Parent: &parentVersion,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureSvcVersionDicts calls `/service//version//dictionaries` API
-func captureSvcVersionDicts(client *http.Client, key string, parentVersion FastlyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersionDictionaries], parentVersion.Metadata["service_id"], parentVersion.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var dicts []Dictionary
-
- if err := json.Unmarshal(respBody, &dicts); err != nil {
- return err
- }
-
- for _, dict := range dicts {
- resource := FastlyResource{
- ID: dict.ID,
- Name: dict.Name,
- Type: TypeSvcVersionDict,
- Parent: &parentVersion,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureSvcVersionBackends calls `/service//version//backend` API
-func captureSvcVersionBackends(client *http.Client, key string, parentVersion FastlyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersionBackends], parentVersion.Metadata["service_id"], parentVersion.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var backends []Backend
-
- if err := json.Unmarshal(respBody, &backends); err != nil {
- return err
- }
-
- for _, backend := range backends {
- resource := FastlyResource{
- ID: parentVersion.Metadata["service_id"] + "/version/" + parentVersion.ID + "/backend/" + backend.Name, // no specific ID
- Name: backend.Name,
- Type: TypeSvcVersionBackend,
- Parent: &parentVersion,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureSvcVersionDomains calls `/service//version//domain` API
-func captureSvcVersionDomains(client *http.Client, key string, parentVersion FastlyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersionDomains], parentVersion.Metadata["service_id"], parentVersion.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var domains []Domain
-
- if err := json.Unmarshal(respBody, &domains); err != nil {
- return err
- }
-
- for _, domain := range domains {
- resource := FastlyResource{
- ID: parentVersion.Metadata["service_id"] + "/version/" + parentVersion.ID + "/domain/" + domain.Name, // no specific ID
- Name: domain.Name,
- Type: TypeSvcVersionDomain,
- Parent: &parentVersion,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureSvcVersionHealthChecks calls `/service//version//healthcheck` API
-func captureSvcVersionHealthChecks(client *http.Client, key string, parentVersion FastlyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersionHealthChecks], parentVersion.Metadata["service_id"], parentVersion.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var healthChecks []HealthCheck
-
- if err := json.Unmarshal(respBody, &healthChecks); err != nil {
- return err
- }
-
- for _, healthCheck := range healthChecks {
- resource := FastlyResource{
- ID: parentVersion.Metadata["service_id"] + "/version/" + parentVersion.ID + "/healthcheck/" + healthCheck.Name, // no specific ID
- Name: healthCheck.Name,
- Type: TypeSvcVersionHealthCheck,
- Parent: &parentVersion,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureConfigStores calls `/resources/stores/config` API
-func captureConfigStores(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, endpoints[configStores], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var configs []ConfigStore
-
- if err := json.Unmarshal(respBody, &configs); err != nil {
- return err
- }
-
- for _, config := range configs {
- resource := FastlyResource{
- ID: config.ID,
- Name: config.Name,
- Type: TypeConfigStore,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureSecretStores calls `/resources/stores/secret` API
-func captureSecretStores(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, endpoints[secretStores], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var secretStores SecretStoreData
-
- if err := json.Unmarshal(respBody, &secretStores); err != nil {
- return err
- }
-
- for _, secret := range secretStores.Data {
- resource := FastlyResource{
- ID: secret.ID,
- Name: secret.Name,
- Type: TypeSecretStore,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// capturePrivateKeys calls `/tls/private_keys` API
-func capturePrivateKeys(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, endpoints[tlsPrivateKeys], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var privateKeys TLSPrivateKeyData
-
- if err := json.Unmarshal(respBody, &privateKeys); err != nil {
- return err
- }
-
- for _, privateKey := range privateKeys.Data {
- resource := FastlyResource{
- ID: privateKey.ID,
- Name: privateKey.Name,
- Type: TypeTLSPrivateKey,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureCertificates calls `/tls/certificates` API
-func captureCertificates(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, endpoints[tlsCertificates], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var certData TLSCertificatesData
-
- if err := json.Unmarshal(respBody, &certData); err != nil {
- return err
- }
-
- for _, cert := range certData.Data {
- resource := FastlyResource{
- ID: cert.ID,
- Name: cert.Name,
- Type: TypeTLSCertificate,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureTLSDomains calls `/tls/domains` API
-func captureTLSDomains(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, endpoints[tlsDomains], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var domainData TLSDomainsData
-
- if err := json.Unmarshal(respBody, &domainData); err != nil {
- return err
- }
-
- for _, domain := range domainData.Data {
- resource := FastlyResource{
- ID: domain.ID,
- Name: domain.ID,
- Type: TypeTLSDomain,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// captureInvoices calls `/billing/v3/invoices` API
-func captureInvoices(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeFastlyRequest(client, endpoints[invoices], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var invoices InvoicesData
-
- if err := json.Unmarshal(respBody, &invoices); err != nil {
- return err
- }
-
- for _, invoice := range invoices.Data {
- resource := FastlyResource{
- ID: invoice.CustomerID + "/region/" + invoice.Region + "/statement/" + invoice.StatementNo + "/invoice/" + invoice.ID,
- Name: invoice.ID, // no specific name
- Type: TypeInvoice,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
diff --git a/pkg/analyzer/analyzers/fastly/result_output.json b/pkg/analyzer/analyzers/fastly/result_output.json
deleted file mode 100644
index 21c84a839edb..000000000000
--- a/pkg/analyzer/analyzers/fastly/result_output.json
+++ /dev/null
@@ -1,294 +0,0 @@
-{
- "AnalyzerType": 34,
- "Bindings": [
- {
- "Resource": {
- "Name": "test",
- "FullyQualifiedName": "Config Store/Q9uDqi7ODnLUrhMFifFVT4",
- "Type": "Config Store",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "centrally-decent-lynx.edgecompute.app",
- "FullyQualifiedName": "Service Version Domain/vInh5jJ0qnGdhiCO04INR7/version/1/domain/centrally-decent-lynx.edgecompute.app",
- "Type": "Service Version Domain",
- "Metadata": {},
- "Parent": {
- "Name": "vInh5jJ0qnGdhiCO04INR7/version/1",
- "FullyQualifiedName": "Service Version/1",
- "Type": "Service Version",
- "Metadata": {
- "service_id": "vInh5jJ0qnGdhiCO04INR7"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "centrally-decent-lynx.edgecompute.app",
- "FullyQualifiedName": "Service Version Domain/vInh5jJ0qnGdhiCO04INR7/version/2/domain/centrally-decent-lynx.edgecompute.app",
- "Type": "Service Version Domain",
- "Metadata": {},
- "Parent": {
- "Name": "vInh5jJ0qnGdhiCO04INR7/version/2",
- "FullyQualifiedName": "Service Version/2",
- "Type": "Service Version",
- "Metadata": {
- "service_id": "vInh5jJ0qnGdhiCO04INR7"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "centrally-decent-lynx.edgecompute.app",
- "FullyQualifiedName": "Service Version Domain/vInh5jJ0qnGdhiCO04INR7/version/3/domain/centrally-decent-lynx.edgecompute.app",
- "Type": "Service Version Domain",
- "Metadata": {},
- "Parent": {
- "Name": "vInh5jJ0qnGdhiCO04INR7/version/3",
- "FullyQualifiedName": "Service Version/3",
- "Type": "Service Version",
- "Metadata": {
- "service_id": "vInh5jJ0qnGdhiCO04INR7"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Detectors",
- "FullyQualifiedName": "Service Version Health Check/vInh5jJ0qnGdhiCO04INR7/version/3/healthcheck/Detectors",
- "Type": "Service Version Health Check",
- "Metadata": {},
- "Parent": {
- "Name": "vInh5jJ0qnGdhiCO04INR7/version/3",
- "FullyQualifiedName": "Service Version/3",
- "Type": "Service Version",
- "Metadata": {
- "service_id": "vInh5jJ0qnGdhiCO04INR7"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "yja0K1GNPRDNTA6vizIFK4/version/1",
- "FullyQualifiedName": "Service Version/1",
- "Type": "Service Version",
- "Metadata": {
- "service_id": "yja0K1GNPRDNTA6vizIFK4"
- },
- "Parent": {
- "Name": "Truffle Security's website",
- "FullyQualifiedName": "Service/yja0K1GNPRDNTA6vizIFK4",
- "Type": "Service",
- "Metadata": {
- "Service Type": "vcl"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "vInh5jJ0qnGdhiCO04INR7/version/1",
- "FullyQualifiedName": "Service Version/1",
- "Type": "Service Version",
- "Metadata": {
- "service_id": "vInh5jJ0qnGdhiCO04INR7"
- },
- "Parent": {
- "Name": "this is a test service",
- "FullyQualifiedName": "Service/vInh5jJ0qnGdhiCO04INR7",
- "Type": "Service",
- "Metadata": {
- "Service Type": "wasm"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "vInh5jJ0qnGdhiCO04INR7/version/2",
- "FullyQualifiedName": "Service Version/2",
- "Type": "Service Version",
- "Metadata": {
- "service_id": "vInh5jJ0qnGdhiCO04INR7"
- },
- "Parent": {
- "Name": "this is a test service",
- "FullyQualifiedName": "Service/vInh5jJ0qnGdhiCO04INR7",
- "Type": "Service",
- "Metadata": {
- "Service Type": "wasm"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "vInh5jJ0qnGdhiCO04INR7/version/3",
- "FullyQualifiedName": "Service Version/3",
- "Type": "Service Version",
- "Metadata": {
- "service_id": "vInh5jJ0qnGdhiCO04INR7"
- },
- "Parent": {
- "Name": "this is a test service",
- "FullyQualifiedName": "Service/vInh5jJ0qnGdhiCO04INR7",
- "Type": "Service",
- "Metadata": {
- "Service Type": "wasm"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "this is a test service",
- "FullyQualifiedName": "Service/vInh5jJ0qnGdhiCO04INR7",
- "Type": "Service",
- "Metadata": {
- "Service Type": "wasm"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security's website",
- "FullyQualifiedName": "Service/yja0K1GNPRDNTA6vizIFK4",
- "Type": "Service",
- "Metadata": {
- "Service Type": "vcl"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "test-user-global",
- "FullyQualifiedName": "User Token/24K13teXo9GhmaUGhwBS2V",
- "Type": "User Token",
- "Metadata": {
- "Expires At": "2025-12-31T19:00:00Z",
- "Role": "",
- "Scope": "global"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "test-user-purge-select",
- "FullyQualifiedName": "User Token/2782vHUyFqralr1GKmWmVF",
- "Type": "User Token",
- "Metadata": {
- "Expires At": "",
- "Role": "",
- "Scope": "purge_select"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "test",
- "FullyQualifiedName": "User Token/278C9jIudzPv9NC6BvZT4z",
- "Type": "User Token",
- "Metadata": {
- "Expires At": "2025-07-22T19:00:00Z",
- "Role": "",
- "Scope": "global:read global"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "integration-test",
- "FullyQualifiedName": "User Token/2ICO7ArmhY8OMiiOyNpXfc",
- "Type": "User Token",
- "Metadata": {
- "Expires At": "",
- "Role": "",
- "Scope": "global:read global"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "global:read global",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {}
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/figma/endpoints.json b/pkg/analyzer/analyzers/figma/endpoints.json
deleted file mode 100644
index 585835617eee..000000000000
--- a/pkg/analyzer/analyzers/figma/endpoints.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "files:read": {
- "url": "https://api.figma.com/v1/me",
- "method": "GET",
- "expected_status_code_with_scope": 200,
- "expected_status_code_without_scope": 403
- },
- "library_analytics:read": {
- "url": "https://api.figma.com/v1/analytics/libraries/0/component/actions",
- "method": "GET",
- "expected_status_code_with_scope": 400,
- "expected_status_code_without_scope": 403
- },
- "file_dev_resources:write": {
- "url": "https://api.figma.com/v1/dev_resources",
- "method": "POST",
- "expected_status_code_with_scope": 400,
- "expected_status_code_without_scope": 403
- },
- "file_variables:read": {
- "url": "https://api.figma.com/v1/files/0/variables/published",
- "method": "GET",
- "expected_status_code_with_scope": 404,
- "expected_status_code_without_scope": 403
- },
- "webhooks:write": {
- "url": "https://api.figma.com/v2/webhooks",
- "method": "POST",
- "expected_status_code_with_scope": 400,
- "expected_status_code_without_scope": 403
- }
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/figma/expected_output.json b/pkg/analyzer/analyzers/figma/expected_output.json
deleted file mode 100644
index 14a248ba6c03..000000000000
--- a/pkg/analyzer/analyzers/figma/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":32,"Bindings":[{"Resource":{"Name":"Source Integration","FullyQualifiedName":"1287160752716166666","Type":"user","Metadata":{"email":"source-integrations@trufflesec.com","img_url":"https://www.gravatar.com/avatar/48da7f448c34d4271a51d2ccf058f473?size=240&default=https%3A%2F%2Fs3-alpha.figma.com%2Fstatic%2Fuser_s_v2.png"},"Parent":null},"Permission":{"Value":"files:read","Parent":null}}],"UnboundedResources":null,"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/figma/figma.go b/pkg/analyzer/analyzers/figma/figma.go
deleted file mode 100644
index 65e5c9c3a729..000000000000
--- a/pkg/analyzer/analyzers/figma/figma.go
+++ /dev/null
@@ -1,222 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go figma
-
-package figma
-
-import (
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "os"
- "regexp"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeFigma }
-
-type ScopeStatus string
-
-const (
- StatusError ScopeStatus = "Error"
- StatusGranted ScopeStatus = "Granted"
- StatusDenied ScopeStatus = "Denied"
- StatusUnverified ScopeStatus = "Unverified"
-)
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- token, ok := credInfo["token"]
- if !ok {
- return nil, errors.New("token not found in credInfo")
- }
- info, err := AnalyzePermissions(a.Cfg, token)
- if err != nil {
- return nil, err
- }
- return MapToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, token string) {
- info, err := AnalyzePermissions(cfg, token)
- if err != nil {
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- color.Green("[!] Valid Figma Personal Access Token\n\n")
- PrintUserAndPermissions(info)
-}
-
-func AnalyzePermissions(cfg *config.Config, token string) (*secretInfo, error) {
- client := analyzers.NewAnalyzeClient(cfg)
- allScopes := getAllScopes()
- scopeToEndpoints, err := getScopeEndpointsMap()
- if err != nil {
- return nil, err
- }
-
- var info = &secretInfo{Scopes: map[Scope]ScopeStatus{}}
- for _, scope := range allScopes {
- info.Scopes[scope] = StatusUnverified
- }
-
- for _, scope := range orderedScopeList {
- endpoint, err := getScopeEndpoint(scopeToEndpoints, scope)
- if err != nil {
- return nil, err
- }
- resp, err := callAPIEndpoint(client, token, endpoint)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
-
- scopeStatus := determineScopeStatus(resp.StatusCode, endpoint)
- if scopeStatus == StatusGranted {
- if scope == ScopeFilesRead {
- if err := json.Unmarshal(body, &info.UserInfo); err != nil {
- return nil, fmt.Errorf("error decoding user info from response %v", err)
- }
- }
- info.Scopes[scope] = StatusGranted
- }
- // If the token does NOT have the scope, response will include all the scopes it does have
- if scopeStatus == StatusDenied {
- scopes, ok := extractScopesFromError(body)
- if !ok {
- return nil, fmt.Errorf("could not extract scopes from error message")
- }
- for scope := range info.Scopes {
- info.Scopes[scope] = StatusDenied
- }
- for _, scope := range scopes {
- info.Scopes[scope] = StatusGranted
- }
- // We have enough info to finish analysis
- break
- }
- }
- return info, nil
-}
-
-// determineScopeStatus takes the API response status code and uses it along with the expected
-// status codes to dermine whether the access token has the required scope to perform that action.
-// It returns a ScopeStatus which can be Granted, Denied, or Unverified.
-func determineScopeStatus(statusCode int, endpoint endpoint) ScopeStatus {
- if statusCode == endpoint.ExpectedStatusCodeWithScope || statusCode == http.StatusOK {
- return StatusGranted
- }
-
- if statusCode == endpoint.ExpectedStatusCodeWithoutScope {
- return StatusDenied
- }
-
- // Can not determine scope as the expected error is unknown
- return StatusUnverified
-}
-
-// Matches API response body with expected message pattern in case the token is missing a scope
-// If the responses match, we can extract all available scopes from the response msg
-func extractScopesFromError(body []byte) ([]Scope, bool) {
- filteredBody := filterErrorResponseBody(string(body))
- re := regexp.MustCompile(`Invalid scope(?:\(s\))?: ([a-zA-Z_:, ]+)\. This endpoint requires.*`)
- matches := re.FindStringSubmatch(filteredBody)
- if len(matches) > 1 {
- scopes := strings.Split(matches[1], ", ")
- return getScopesFromScopeStrings(scopes), true
- }
- return nil, false
-}
-
-// The filterErrorResponseBody function cleans the provided "invalid permission" API
-// response message by removing the characters '"', '[', ']', '\', and '"'.
-func filterErrorResponseBody(msg string) string {
- result := strings.ReplaceAll(msg, "\\", "")
- result = strings.ReplaceAll(result, "\"", "")
- result = strings.ReplaceAll(result, "[", "")
- return strings.ReplaceAll(result, "]", "")
-}
-
-func MapToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeFigma,
- }
- var permissions []analyzers.Permission
- for scope, status := range info.Scopes {
- if status != StatusGranted {
- continue
- }
- permissions = append(permissions, analyzers.Permission{Value: string(scope)})
- }
- userResource := analyzers.Resource{
- Name: info.UserInfo.Handle,
- FullyQualifiedName: info.UserInfo.ID,
- Type: "user",
- Metadata: map[string]any{
- "email": info.UserInfo.Email,
- "img_url": info.UserInfo.ImgURL,
- },
- }
-
- result.Bindings = analyzers.BindAllPermissions(userResource, permissions...)
- return &result
-}
-
-func PrintUserAndPermissions(info *secretInfo) {
- color.Yellow("[i] User Info:")
- t1 := table.NewWriter()
- t1.SetOutputMirror(os.Stdout)
- t1.AppendHeader(table.Row{"ID", "Handle", "Email", "Image URL"})
- t1.AppendRow(table.Row{
- color.GreenString(info.UserInfo.ID),
- color.GreenString(info.UserInfo.Handle),
- color.GreenString(info.UserInfo.Email),
- color.GreenString(info.UserInfo.ImgURL),
- })
- t1.SetOutputMirror(os.Stdout)
- t1.Render()
-
- color.Yellow("\n[i] Scopes:")
- t2 := table.NewWriter()
- t2.AppendHeader(table.Row{"Scope", "Status", "Actions"})
- for scope, status := range info.Scopes {
- actions := getScopeActions(scope)
- rows := []table.Row{}
- for i, action := range actions {
- var scopeCell string
- var statusCell string
- if i == 0 {
- scopeCell = color.GreenString(string(scope))
- statusCell = color.GreenString(string(status))
- }
- rows = append(rows, table.Row{scopeCell, statusCell, color.GreenString(action)})
- }
- t2.AppendRows(rows)
- t2.AppendSeparator()
- }
- t2.SetOutputMirror(os.Stdout)
- t2.Render()
- fmt.Printf("%s: https://www.figma.com/developers/api\n\n", color.GreenString("Ref"))
-}
diff --git a/pkg/analyzer/analyzers/figma/figma_test.go b/pkg/analyzer/analyzers/figma/figma_test.go
deleted file mode 100644
index dcf805b16dea..000000000000
--- a/pkg/analyzer/analyzers/figma/figma_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package figma
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- token string
- want string // JSON string
- wantErr bool
- }{
- {
- token: testSecrets.MustGetField("FIGMAPERSONALACCESSTOKEN_V2_TOKEN"),
- name: "valid Figma Personal Access Token",
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"token": tt.token})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/figma/models.go b/pkg/analyzer/analyzers/figma/models.go
deleted file mode 100644
index d843a21e0f52..000000000000
--- a/pkg/analyzer/analyzers/figma/models.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package figma
-
-type userInfo struct {
- ID string `json:"id"`
- Handle string `json:"handle"`
- ImgURL string `json:"img_url"`
- Email string `json:"email"`
-}
-
-type secretInfo struct {
- UserInfo userInfo
- Scopes map[Scope]ScopeStatus
-}
-
-type endpoint struct {
- URL string `json:"url"`
- Method string `json:"method"`
- ExpectedStatusCodeWithScope int `json:"expected_status_code_with_scope"`
- ExpectedStatusCodeWithoutScope int `json:"expected_status_code_without_scope"`
-}
diff --git a/pkg/analyzer/analyzers/figma/permissions.go b/pkg/analyzer/analyzers/figma/permissions.go
deleted file mode 100644
index 9431a562ed89..000000000000
--- a/pkg/analyzer/analyzers/figma/permissions.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package figma
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- FilesRead Permission = iota
- FileVariablesRead Permission = iota
- FileVariablesWrite Permission = iota
- FileCommentsWrite Permission = iota
- FileDevResourcesRead Permission = iota
- FileDevResourcesWrite Permission = iota
- LibraryAnalyticsRead Permission = iota
- WebhooksWrite Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- FilesRead: "files:read",
- FileVariablesRead: "file_variables:read",
- FileVariablesWrite: "file_variables:write",
- FileCommentsWrite: "file_comments:write",
- FileDevResourcesRead: "file_dev_resources:read",
- FileDevResourcesWrite: "file_dev_resources:write",
- LibraryAnalyticsRead: "library_analytics:read",
- WebhooksWrite: "webhooks:write",
- }
-
- StringToPermission = map[string]Permission{
- "files:read": FilesRead,
- "file_variables:read": FileVariablesRead,
- "file_variables:write": FileVariablesWrite,
- "file_comments:write": FileCommentsWrite,
- "file_dev_resources:read": FileDevResourcesRead,
- "file_dev_resources:write": FileDevResourcesWrite,
- "library_analytics:read": LibraryAnalyticsRead,
- "webhooks:write": WebhooksWrite,
- }
-
- PermissionIDs = map[Permission]int{
- FilesRead: 1,
- FileVariablesRead: 2,
- FileVariablesWrite: 3,
- FileCommentsWrite: 4,
- FileDevResourcesRead: 5,
- FileDevResourcesWrite: 6,
- LibraryAnalyticsRead: 7,
- WebhooksWrite: 8,
- }
-
- IdToPermission = map[int]Permission{
- 1: FilesRead,
- 2: FileVariablesRead,
- 3: FileVariablesWrite,
- 4: FileCommentsWrite,
- 5: FileDevResourcesRead,
- 6: FileDevResourcesWrite,
- 7: LibraryAnalyticsRead,
- 8: WebhooksWrite,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/figma/permissions.yaml b/pkg/analyzer/analyzers/figma/permissions.yaml
deleted file mode 100644
index d2af60e1e2db..000000000000
--- a/pkg/analyzer/analyzers/figma/permissions.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
-permissions:
- - files:read
- - file_variables:read
- - file_variables:write
- - file_comments:write
- - file_dev_resources:read
- - file_dev_resources:write
- - library_analytics:read
- - webhooks:write
diff --git a/pkg/analyzer/analyzers/figma/requests.go b/pkg/analyzer/analyzers/figma/requests.go
deleted file mode 100644
index e78f3676e234..000000000000
--- a/pkg/analyzer/analyzers/figma/requests.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package figma
-
-import (
- "net/http"
-)
-
-func callAPIEndpoint(client *http.Client, token string, endpoint endpoint) (*http.Response, error) {
- req, err := http.NewRequest(endpoint.Method, endpoint.URL, nil)
- if err != nil {
- return nil, err
- }
- req.Header.Set("X-FIGMA-TOKEN", token)
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
- }
-
- return resp, nil
-}
diff --git a/pkg/analyzer/analyzers/figma/scopes.go b/pkg/analyzer/analyzers/figma/scopes.go
deleted file mode 100644
index 3ac37b224a80..000000000000
--- a/pkg/analyzer/analyzers/figma/scopes.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package figma
-
-import (
- _ "embed"
- "encoding/json"
- "errors"
-)
-
-type Scope string
-
-const (
- ScopeFilesRead Scope = "files:read"
- ScopeFileVariablesRead Scope = "file_variables:read"
- ScopeFileVariablesWrite Scope = "file_variables:write"
- ScopeFileCommentsWrite Scope = "file_comments:write"
- ScopeFileDevResourcesRead Scope = "file_dev_resources:read"
- ScopeFileDevResourcesWrite Scope = "file_dev_resources:write"
- ScopeLibraryAnalyticsRead Scope = "library_analytics:read"
- ScopeWebhooksWrite Scope = "webhooks:write"
-)
-
-// This list orders the scope in which they must be tested
-var orderedScopeList = []Scope{
- ScopeFilesRead,
- ScopeLibraryAnalyticsRead,
- ScopeFileDevResourcesWrite,
- ScopeFileVariablesRead,
- ScopeWebhooksWrite,
-}
-
-var scopeToActions = map[Scope][]string{
- ScopeFilesRead: {
- "Get user info",
- "Read files",
- "Read projects",
- "Read users",
- "Read versions",
- "Read comments",
- "Read components & styles",
- "Read webhooks",
- },
- ScopeFileVariablesRead: {
- "Read file variables",
- },
- ScopeFileVariablesWrite: {
- "Write file variables",
- },
- ScopeFileCommentsWrite: {
- "Post comments",
- "Delete comments",
- "Post comment reactions",
- "Delete comment reactions",
- },
- ScopeFileDevResourcesRead: {
- "Read file dev resources",
- },
- ScopeFileDevResourcesWrite: {
- "Write file dev resources",
- },
- ScopeLibraryAnalyticsRead: {
- "Read design system analytics",
- },
- ScopeWebhooksWrite: {
- "Create webhooks",
- "Manage webhooks",
- },
-}
-
-var scopeStringToScope map[string]Scope
-
-//go:embed endpoints.json
-var endpointsConfig []byte
-
-func init() {
- scopeStringToScope = map[string]Scope{
- string(ScopeFilesRead): ScopeFilesRead,
- string(ScopeFileVariablesRead): ScopeFileVariablesRead,
- string(ScopeFileVariablesWrite): ScopeFileVariablesWrite,
- string(ScopeFileCommentsWrite): ScopeFileCommentsWrite,
- string(ScopeFileDevResourcesRead): ScopeFileDevResourcesRead,
- string(ScopeFileDevResourcesWrite): ScopeFileDevResourcesWrite,
- string(ScopeLibraryAnalyticsRead): ScopeLibraryAnalyticsRead,
- string(ScopeWebhooksWrite): ScopeWebhooksWrite,
- }
-}
-
-func getScopeActions(scope Scope) []string {
- return scopeToActions[scope]
-}
-
-func getScopeEndpointsMap() (map[Scope]endpoint, error) {
- var scopeToEndpoints map[Scope]endpoint
- if err := json.Unmarshal(endpointsConfig, &scopeToEndpoints); err != nil {
- return nil, errors.New("failed to unmarshal endpoints.json: " + err.Error())
- }
- return scopeToEndpoints, nil
-}
-
-func getScopeEndpoint(scopeToEndpoint map[Scope]endpoint, scope Scope) (endpoint, error) {
- if endpoint, ok := scopeToEndpoint[scope]; ok {
- return endpoint, nil
- }
- return endpoint{}, errors.New("invalid scope or endpoint doesn't exist")
-}
-
-func getScopesFromScopeStrings(scopeStrings []string) []Scope {
- var scopes []Scope
- for _, scopeString := range scopeStrings {
- if scope, ok := scopeStringToScope[scopeString]; ok {
- scopes = append(scopes, scope)
- }
- }
- return scopes
-}
-
-func getAllScopes() []Scope {
- return []Scope{
- ScopeFilesRead,
- ScopeFileVariablesRead,
- ScopeFileVariablesWrite,
- ScopeFileCommentsWrite,
- ScopeFileDevResourcesRead,
- ScopeFileDevResourcesWrite,
- ScopeLibraryAnalyticsRead,
- ScopeWebhooksWrite,
- }
-}
diff --git a/pkg/analyzer/analyzers/github/classic/classic.yaml b/pkg/analyzer/analyzers/github/classic/classic.yaml
deleted file mode 100644
index 8383c50c26a4..000000000000
--- a/pkg/analyzer/analyzers/github/classic/classic.yaml
+++ /dev/null
@@ -1,49 +0,0 @@
-permissions:
- - repo
- - repo:status
- - repo_deployment
- - public_repo
- - repo:invite
- - security_events
- - workflow
- - write:packages
- - read:packages
- - delete:packages
- - admin:org
- - write:org
- - read:org
- - manage_runners:org
- - admin:public_key
- - write:public_key
- - read:public_key
- - admin:repo_hook
- - write:repo_hook
- - read:repo_hook
- - admin:org_hook
- - gist
- - notifications
- - user
- - read:user
- - user:email
- - user:follow
- - delete_repo
- - write:discussion
- - read:discussion
- - admin:enterprise
- - manage_runners:enterprise
- - manage_billing:enterprise
- - read:enterprise
- - audit_log
- - read:audit_log
- - codespace
- - codespace:secrets
- - copilot
- - manage_billing:copilot
- - project
- - read:project
- - admin:gpg_key
- - write:gpg_key
- - read:gpg_key
- - admin:ssh_signing_key
- - write:ssh_signing_key
- - read:ssh_signing_key
diff --git a/pkg/analyzer/analyzers/github/classic/classic_permissions.go b/pkg/analyzer/analyzers/github/classic/classic_permissions.go
deleted file mode 100644
index 230e2fbba13c..000000000000
--- a/pkg/analyzer/analyzers/github/classic/classic_permissions.go
+++ /dev/null
@@ -1,296 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package classic
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Repo Permission = iota
- RepoStatus Permission = iota
- RepoDeployment Permission = iota
- PublicRepo Permission = iota
- RepoInvite Permission = iota
- SecurityEvents Permission = iota
- Workflow Permission = iota
- WritePackages Permission = iota
- ReadPackages Permission = iota
- DeletePackages Permission = iota
- AdminOrg Permission = iota
- WriteOrg Permission = iota
- ReadOrg Permission = iota
- ManageRunnersOrg Permission = iota
- AdminPublicKey Permission = iota
- WritePublicKey Permission = iota
- ReadPublicKey Permission = iota
- AdminRepoHook Permission = iota
- WriteRepoHook Permission = iota
- ReadRepoHook Permission = iota
- AdminOrgHook Permission = iota
- Gist Permission = iota
- Notifications Permission = iota
- User Permission = iota
- ReadUser Permission = iota
- UserEmail Permission = iota
- UserFollow Permission = iota
- DeleteRepo Permission = iota
- WriteDiscussion Permission = iota
- ReadDiscussion Permission = iota
- AdminEnterprise Permission = iota
- ManageRunnersEnterprise Permission = iota
- ManageBillingEnterprise Permission = iota
- ReadEnterprise Permission = iota
- AuditLog Permission = iota
- ReadAuditLog Permission = iota
- Codespace Permission = iota
- CodespaceSecrets Permission = iota
- Copilot Permission = iota
- ManageBillingCopilot Permission = iota
- Project Permission = iota
- ReadProject Permission = iota
- AdminGpgKey Permission = iota
- WriteGpgKey Permission = iota
- ReadGpgKey Permission = iota
- AdminSshSigningKey Permission = iota
- WriteSshSigningKey Permission = iota
- ReadSshSigningKey Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Repo: "repo",
- RepoStatus: "repo:status",
- RepoDeployment: "repo_deployment",
- PublicRepo: "public_repo",
- RepoInvite: "repo:invite",
- SecurityEvents: "security_events",
- Workflow: "workflow",
- WritePackages: "write:packages",
- ReadPackages: "read:packages",
- DeletePackages: "delete:packages",
- AdminOrg: "admin:org",
- WriteOrg: "write:org",
- ReadOrg: "read:org",
- ManageRunnersOrg: "manage_runners:org",
- AdminPublicKey: "admin:public_key",
- WritePublicKey: "write:public_key",
- ReadPublicKey: "read:public_key",
- AdminRepoHook: "admin:repo_hook",
- WriteRepoHook: "write:repo_hook",
- ReadRepoHook: "read:repo_hook",
- AdminOrgHook: "admin:org_hook",
- Gist: "gist",
- Notifications: "notifications",
- User: "user",
- ReadUser: "read:user",
- UserEmail: "user:email",
- UserFollow: "user:follow",
- DeleteRepo: "delete_repo",
- WriteDiscussion: "write:discussion",
- ReadDiscussion: "read:discussion",
- AdminEnterprise: "admin:enterprise",
- ManageRunnersEnterprise: "manage_runners:enterprise",
- ManageBillingEnterprise: "manage_billing:enterprise",
- ReadEnterprise: "read:enterprise",
- AuditLog: "audit_log",
- ReadAuditLog: "read:audit_log",
- Codespace: "codespace",
- CodespaceSecrets: "codespace:secrets",
- Copilot: "copilot",
- ManageBillingCopilot: "manage_billing:copilot",
- Project: "project",
- ReadProject: "read:project",
- AdminGpgKey: "admin:gpg_key",
- WriteGpgKey: "write:gpg_key",
- ReadGpgKey: "read:gpg_key",
- AdminSshSigningKey: "admin:ssh_signing_key",
- WriteSshSigningKey: "write:ssh_signing_key",
- ReadSshSigningKey: "read:ssh_signing_key",
- }
-
- StringToPermission = map[string]Permission{
- "repo": Repo,
- "repo:status": RepoStatus,
- "repo_deployment": RepoDeployment,
- "public_repo": PublicRepo,
- "repo:invite": RepoInvite,
- "security_events": SecurityEvents,
- "workflow": Workflow,
- "write:packages": WritePackages,
- "read:packages": ReadPackages,
- "delete:packages": DeletePackages,
- "admin:org": AdminOrg,
- "write:org": WriteOrg,
- "read:org": ReadOrg,
- "manage_runners:org": ManageRunnersOrg,
- "admin:public_key": AdminPublicKey,
- "write:public_key": WritePublicKey,
- "read:public_key": ReadPublicKey,
- "admin:repo_hook": AdminRepoHook,
- "write:repo_hook": WriteRepoHook,
- "read:repo_hook": ReadRepoHook,
- "admin:org_hook": AdminOrgHook,
- "gist": Gist,
- "notifications": Notifications,
- "user": User,
- "read:user": ReadUser,
- "user:email": UserEmail,
- "user:follow": UserFollow,
- "delete_repo": DeleteRepo,
- "write:discussion": WriteDiscussion,
- "read:discussion": ReadDiscussion,
- "admin:enterprise": AdminEnterprise,
- "manage_runners:enterprise": ManageRunnersEnterprise,
- "manage_billing:enterprise": ManageBillingEnterprise,
- "read:enterprise": ReadEnterprise,
- "audit_log": AuditLog,
- "read:audit_log": ReadAuditLog,
- "codespace": Codespace,
- "codespace:secrets": CodespaceSecrets,
- "copilot": Copilot,
- "manage_billing:copilot": ManageBillingCopilot,
- "project": Project,
- "read:project": ReadProject,
- "admin:gpg_key": AdminGpgKey,
- "write:gpg_key": WriteGpgKey,
- "read:gpg_key": ReadGpgKey,
- "admin:ssh_signing_key": AdminSshSigningKey,
- "write:ssh_signing_key": WriteSshSigningKey,
- "read:ssh_signing_key": ReadSshSigningKey,
- }
-
- PermissionIDs = map[Permission]int{
- Repo: 1,
- RepoStatus: 2,
- RepoDeployment: 3,
- PublicRepo: 4,
- RepoInvite: 5,
- SecurityEvents: 6,
- Workflow: 7,
- WritePackages: 8,
- ReadPackages: 9,
- DeletePackages: 10,
- AdminOrg: 11,
- WriteOrg: 12,
- ReadOrg: 13,
- ManageRunnersOrg: 14,
- AdminPublicKey: 15,
- WritePublicKey: 16,
- ReadPublicKey: 17,
- AdminRepoHook: 18,
- WriteRepoHook: 19,
- ReadRepoHook: 20,
- AdminOrgHook: 21,
- Gist: 22,
- Notifications: 23,
- User: 24,
- ReadUser: 25,
- UserEmail: 26,
- UserFollow: 27,
- DeleteRepo: 28,
- WriteDiscussion: 29,
- ReadDiscussion: 30,
- AdminEnterprise: 31,
- ManageRunnersEnterprise: 32,
- ManageBillingEnterprise: 33,
- ReadEnterprise: 34,
- AuditLog: 35,
- ReadAuditLog: 36,
- Codespace: 37,
- CodespaceSecrets: 38,
- Copilot: 39,
- ManageBillingCopilot: 40,
- Project: 41,
- ReadProject: 42,
- AdminGpgKey: 43,
- WriteGpgKey: 44,
- ReadGpgKey: 45,
- AdminSshSigningKey: 46,
- WriteSshSigningKey: 47,
- ReadSshSigningKey: 48,
- }
-
- IdToPermission = map[int]Permission{
- 1: Repo,
- 2: RepoStatus,
- 3: RepoDeployment,
- 4: PublicRepo,
- 5: RepoInvite,
- 6: SecurityEvents,
- 7: Workflow,
- 8: WritePackages,
- 9: ReadPackages,
- 10: DeletePackages,
- 11: AdminOrg,
- 12: WriteOrg,
- 13: ReadOrg,
- 14: ManageRunnersOrg,
- 15: AdminPublicKey,
- 16: WritePublicKey,
- 17: ReadPublicKey,
- 18: AdminRepoHook,
- 19: WriteRepoHook,
- 20: ReadRepoHook,
- 21: AdminOrgHook,
- 22: Gist,
- 23: Notifications,
- 24: User,
- 25: ReadUser,
- 26: UserEmail,
- 27: UserFollow,
- 28: DeleteRepo,
- 29: WriteDiscussion,
- 30: ReadDiscussion,
- 31: AdminEnterprise,
- 32: ManageRunnersEnterprise,
- 33: ManageBillingEnterprise,
- 34: ReadEnterprise,
- 35: AuditLog,
- 36: ReadAuditLog,
- 37: Codespace,
- 38: CodespaceSecrets,
- 39: Copilot,
- 40: ManageBillingCopilot,
- 41: Project,
- 42: ReadProject,
- 43: AdminGpgKey,
- 44: WriteGpgKey,
- 45: ReadGpgKey,
- 46: AdminSshSigningKey,
- 47: WriteSshSigningKey,
- 48: ReadSshSigningKey,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/github/classic/classictoken.go b/pkg/analyzer/analyzers/github/classic/classictoken.go
deleted file mode 100644
index f6cce8ef99d6..000000000000
--- a/pkg/analyzer/analyzers/github/classic/classictoken.go
+++ /dev/null
@@ -1,290 +0,0 @@
-//go:generate generate_permissions classic.yaml classic_permissions.go classic
-
-package classic
-
-import (
- "fmt"
- "os"
- "strings"
-
- "github.com/fatih/color"
- gh "github.com/google/go-github/v67/github"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
-)
-
-var SCOPE_ORDER = [][]Permission{
- {Repo, RepoStatus, RepoDeployment, PublicRepo, RepoInvite, SecurityEvents},
- {Workflow},
- {WritePackages, ReadPackages},
- {DeletePackages},
- {AdminOrg, WriteOrg, ReadOrg, ManageRunnersOrg},
- {AdminPublicKey, WritePublicKey, ReadPublicKey},
- {AdminRepoHook, WriteRepoHook, ReadRepoHook},
- {AdminOrgHook},
- {Gist},
- {Notifications},
- {User, ReadUser, UserEmail, UserFollow},
- {DeleteRepo},
- {WriteDiscussion, ReadDiscussion},
- {AdminEnterprise, ManageRunnersEnterprise, ManageBillingEnterprise, ReadEnterprise},
- {AuditLog, ReadAuditLog},
- {Codespace, CodespaceSecrets},
- {Copilot, ManageBillingCopilot},
- {Project, ReadProject},
- {AdminGpgKey, WriteGpgKey, ReadGpgKey},
- {AdminSshSigningKey, WriteSshSigningKey, ReadSshSigningKey},
-}
-
-var SCOPE_TO_SUB_SCOPE = map[Permission][]Permission{
- Repo: {RepoStatus, RepoDeployment, PublicRepo, RepoInvite, SecurityEvents},
- WritePackages: {ReadPackages},
- AdminOrg: {WriteOrg, ReadOrg, ManageRunnersOrg},
- WriteOrg: {ReadOrg},
- AdminPublicKey: {WritePublicKey, ReadPublicKey},
- WritePublicKey: {ReadPublicKey},
- AdminRepoHook: {WriteRepoHook, ReadRepoHook},
- WriteRepoHook: {ReadRepoHook},
- User: {ReadUser, UserEmail, UserFollow},
- WriteDiscussion: {ReadDiscussion},
- AdminEnterprise: {ManageRunnersEnterprise, ManageBillingEnterprise, ReadEnterprise},
- ManageBillingEnterprise: {ReadEnterprise},
- AuditLog: {ReadAuditLog},
- Codespace: {CodespaceSecrets},
- Copilot: {ManageBillingCopilot},
- Project: {ReadProject},
- AdminGpgKey: {WriteGpgKey, ReadGpgKey},
- WriteGpgKey: {ReadGpgKey},
- AdminSshSigningKey: {WriteSshSigningKey, ReadSshSigningKey},
- WriteSshSigningKey: {ReadSshSigningKey},
-}
-
-func hasPrivateRepoAccess(scopes map[Permission]bool) bool {
- return scopes[Repo]
-}
-
-func processScopes(headerScopesSlice []analyzers.Permission) map[Permission]bool {
- allScopes := make(map[Permission]bool)
- for _, scope := range headerScopesSlice {
- allScopes[StringToPermission[scope.Value]] = true
- }
- for scope := range allScopes {
- if subScopes, ok := SCOPE_TO_SUB_SCOPE[scope]; ok {
- for _, subScope := range subScopes {
- allScopes[subScope] = true
- }
- }
- }
- return allScopes
-}
-
-func AnalyzeClassicToken(client *gh.Client, meta *common.TokenMetadata) (*common.SecretInfo, error) {
- // Convert OauthScopes to have hierarchical permissions.
- meta.OauthScopes = oauthScopesToPermissions(meta.OauthScopes...)
- scopes := processScopes(meta.OauthScopes)
-
- var repos []*gh.Repository
- if hasPrivateRepoAccess(scopes) {
- var err error
- repos, err = common.GetAllReposForUser(client)
- if err != nil {
- return nil, err
- }
- }
-
- gists, err := common.GetAllGistsForUser(client)
- if err != nil {
- return nil, err
- }
-
- return &common.SecretInfo{
- Metadata: meta,
- Repos: repos,
- Gists: gists,
- }, nil
-}
-
-func filterPrivateRepoScopes(scopes map[Permission]bool) []Permission {
- var intersection []Permission
- privateScopes := []Permission{Repo, RepoStatus, RepoDeployment, RepoInvite, SecurityEvents, AdminRepoHook, WriteRepoHook, ReadRepoHook}
-
- for _, privScope := range privateScopes {
- if scopes[privScope] {
- intersection = append(intersection, privScope)
- }
- }
- return intersection
-}
-
-func PrintClassicToken(cfg *config.Config, info *common.SecretInfo) {
- scopes := processScopes(info.Metadata.OauthScopes)
- if len(scopes) == 0 {
- color.Red("[x] Classic Token has no scopes")
- } else {
- printClassicGHPermissions(scopes, cfg.ShowAll)
- }
-
- privateScopes := filterPrivateRepoScopes(scopes)
- if hasPrivateRepoAccess(scopes) {
- color.Green("[!] Token has scope(s) for both public and private repositories. Here's a list of all accessible repositories:")
- common.PrintGitHubRepos(info.Repos)
- } else if len(privateScopes) > 0 {
- color.Yellow("[!] Token has scope(s) useful for accessing both public and private repositories.\n However, without the `repo` scope, we cannot enumerate or access code from private repos.\n Review the permissions associated with the following scopes for more details: %v", joinPermissions(privateScopes))
- } else if scopes[PublicRepo] {
- color.Yellow("[i] Token is scoped to only public repositories. See https://github.com/%v?tab=repositories", *info.Metadata.User.Login)
- } else {
- color.Red("[x] Token does not appear scoped to any specific repositories.")
- }
- common.PrintGists(info.Gists, cfg.ShowAll)
-}
-
-func joinPermissions(perms []Permission) string {
- var permStrings []string
- for _, perm := range perms {
- permStr, err := perm.ToString()
- if err != nil {
- panic(err)
- }
- permStrings = append(permStrings, permStr)
- }
- return strings.Join(permStrings, ", ")
-}
-
-func scopeFormatter(scope Permission, checked bool, indentation int) (string, string) {
- scopeStr, err := scope.ToString()
- if err != nil {
- panic(err)
- }
- if indentation != 0 {
- scopeStr = strings.Repeat(" ", indentation) + scopeStr
- }
- if checked {
- return color.GreenString(scopeStr), color.GreenString("true")
- }
- return scopeStr, "false"
-}
-
-func printClassicGHPermissions(scopes map[Permission]bool, showAll bool) {
- scopeCount := 0
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Scope", "In-Scope"})
-
- filteredScopes := make([][]Permission, 0)
- for _, scopeSlice := range SCOPE_ORDER {
- for _, scope := range scopeSlice {
- if scopes[scope] {
- filteredScopes = append(filteredScopes, scopeSlice)
- break
- }
- }
- }
-
- var formattedScope, status string
- var indentation int
-
- if !showAll {
- for _, scopeSlice := range filteredScopes {
- for ind, scope := range scopeSlice {
- if ind == 0 {
- indentation = 0
- if scopes[scope] {
- scopeCount++
- formattedScope, status = scopeFormatter(scope, true, indentation)
- t.AppendRow([]any{formattedScope, status})
- } else {
-
- scopeStr, err := scope.ToString()
- if err != nil {
- panic(err)
- }
- t.AppendRow([]any{scopeStr, "----"})
- }
- } else {
- indentation = 2
- if scopes[scope] {
- scopeCount++
- formattedScope, status = scopeFormatter(scope, true, indentation)
- t.AppendRow([]any{formattedScope, status})
- }
- }
- }
- t.AppendSeparator()
- }
- } else {
- for _, scopeSlice := range SCOPE_ORDER {
- for ind, scope := range scopeSlice {
- if ind == 0 {
- indentation = 0
- } else {
- indentation = 2
- }
- if scopes[scope] {
- scopeCount++
- formattedScope, status = scopeFormatter(scope, true, indentation)
- t.AppendRow([]any{formattedScope, status})
- } else {
- formattedScope, status = scopeFormatter(scope, false, indentation)
- t.AppendRow([]any{formattedScope, status})
- }
- }
- t.AppendSeparator()
- }
- }
-
- if scopeCount == 0 && !showAll {
- color.Red("No Scopes Found for the GitHub Token above\n\n")
- return
- } else if scopeCount == 0 {
- color.Red("Found No Scopes for the GitHub Token above\n")
- } else {
- color.Green(fmt.Sprintf("[!] Found %v Scope(s) for the GitHub Token above\n", scopeCount))
- }
- t.Render()
- fmt.Print("\n\n")
-}
-
-// oauthScopesToPermissions takes a list of scopes and returns a slice of
-// permissions for it. If the scope has implied permissions, they are included
-// as children of the parent scope, and both the parent and children are
-// returned in the slice.
-func oauthScopesToPermissions(scopes ...analyzers.Permission) []analyzers.Permission {
- allPermissions := make([]analyzers.Permission, 0, len(scopes))
- for _, scope := range scopes {
- allPermissions = append(allPermissions, oauthScopeToPermissions(scope.Value)...)
- }
- return allPermissions
-}
-
-// oauthScopeToPermissions takes a given scope and returns a slice of
-// permissions for it. If the scope has implied permissions, they are included
-// as children of the parent scope, and both the parent and children are
-// returned in the slice.
-func oauthScopeToPermissions(scope string) []analyzers.Permission {
- parent := analyzers.Permission{Value: scope}
- perms := []analyzers.Permission{parent}
- subScopes, ok := func() ([]Permission, bool) {
- id, err := PermissionFromString(scope)
- if err != nil {
- return nil, false
- }
- subScopes, ok := SCOPE_TO_SUB_SCOPE[id]
- return subScopes, ok
- }()
- if !ok {
- // No sub-scopes, so the only permission is itself.
- return perms
- }
- // Add all the children to the list of permissions.
- for _, subScope := range subScopes {
- subScope, _ := subScope.ToString()
- perms = append(perms, analyzers.Permission{
- Value: subScope,
- Parent: &parent,
- })
- }
- return perms
-}
diff --git a/pkg/analyzer/analyzers/github/common/github.go b/pkg/analyzer/analyzers/github/common/github.go
deleted file mode 100644
index 1804e4a3f305..000000000000
--- a/pkg/analyzer/analyzers/github/common/github.go
+++ /dev/null
@@ -1,207 +0,0 @@
-package common
-
-import (
- "context"
- "fmt"
- "os"
- "strings"
- "time"
-
- "github.com/fatih/color"
- gh "github.com/google/go-github/v67/github"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
-)
-
-type TokenType string
-
-const (
- TokenTypeFineGrainedPAT TokenType = "Fine-Grained GitHub Personal Access Token"
- TokenTypeClassicPAT TokenType = "Classic GitHub Personal Access Token"
- TokenTypeUserToServer TokenType = "GitHub User-to-Server Token"
- TokenTypeGitHubToken TokenType = "GitHub Token"
-)
-
-func checkFineGrained(token string, oauthScopes []analyzers.Permission) (TokenType, bool) {
- // For details on token prefixes, see:
- // https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/
-
- // Special case for ghu_ prefix tokens (ex: in a codespace) that don't have the X-OAuth-Scopes header
- if strings.HasPrefix(token, "ghu_") {
- return TokenTypeUserToServer, true
- }
-
- // Handle github_pat_ tokens
- if strings.HasPrefix(token, "github_pat") {
- return TokenTypeFineGrainedPAT, true
- }
-
- // Handle classic PATs
- if strings.HasPrefix(token, "ghp_") {
- return TokenTypeClassicPAT, false
- }
-
- // Catch-all for any other types
- // If resp.Header "X-OAuth-Scopes" doesn't exist, then we have fine-grained permissions
- if len(oauthScopes) > 0 {
- return TokenTypeGitHubToken, false
- }
- return TokenTypeGitHubToken, true
-}
-
-type Permission int
-
-type SecretInfo struct {
- Metadata *TokenMetadata
- Repos []*gh.Repository
- Gists []*gh.Gist
- // AccessibleRepos, RepoAccessMap, and UserAccessMap are only set if
- // the token has fine-grained access.
- AccessibleRepos []*gh.Repository
- RepoAccessMap any
- UserAccessMap any
-}
-
-type TokenMetadata struct {
- Type TokenType
- FineGrained bool
- User *gh.User
- Expiration time.Time
- // OauthScopes is only set for classic tokens.
- OauthScopes []analyzers.Permission
-}
-
-// GetTokenMetadata gets the username, expiration date, and x-oauth-scopes headers for a given token
-// by sending a GET request to the /user endpoint
-// Returns a response object for usage in the checkFineGrained function
-func GetTokenMetadata(token string, client *gh.Client) (*TokenMetadata, error) {
- user, resp, err := client.Users.Get(context.Background(), "")
- if err != nil {
- return nil, err
- }
-
- var oauthScopes []analyzers.Permission
- for _, scope := range resp.Header.Values("X-OAuth-Scopes") {
- for _, scope := range strings.Split(scope, ", ") {
- oauthScopes = append(oauthScopes, analyzers.Permission{Value: scope})
- }
- }
- tokenType, fineGrained := checkFineGrained(token, oauthScopes)
-
- var expiration time.Time
- if tokenType == TokenTypeClassicPAT {
- // for classic tokens, github return token expiration time in header in UTC format.
- expiration, _ = time.Parse("2006-01-02 15:04:05 UTC", resp.Header.Get("github-authentication-token-expiration"))
- } else {
- expiration, _ = time.Parse("2006-01-02 15:04:05 -0700", resp.Header.Get("github-authentication-token-expiration"))
- }
-
- return &TokenMetadata{
- Type: tokenType,
- FineGrained: fineGrained,
- User: user,
- Expiration: expiration,
- OauthScopes: oauthScopes,
- }, nil
-}
-
-func GetAllGistsForUser(client *gh.Client) ([]*gh.Gist, error) {
- opt := &gh.GistListOptions{ListOptions: gh.ListOptions{PerPage: 100}}
- var allGists []*gh.Gist
- page := 1
- for {
- opt.Page = page
- gists, resp, err := client.Gists.List(context.Background(), "", opt)
- if err != nil {
- color.Red("Error getting gists.")
- return nil, err
- }
- allGists = append(allGists, gists...)
-
- linkHeader := resp.Header.Get("link")
- if linkHeader == "" || !strings.Contains(linkHeader, `rel="next"`) {
- break
- }
- page++
-
- }
-
- return allGists, nil
-}
-
-func GetAllReposForUser(client *gh.Client) ([]*gh.Repository, error) {
- opt := &gh.RepositoryListByAuthenticatedUserOptions{ListOptions: gh.ListOptions{PerPage: 100}}
- var allRepos []*gh.Repository
- page := 1
- for {
- opt.Page = page
- repos, resp, err := client.Repositories.ListByAuthenticatedUser(context.Background(), opt)
- if err != nil {
- color.Red("Error getting repos.")
- return nil, err
- }
- allRepos = append(allRepos, repos...)
-
- linkHeader := resp.Header.Get("link")
- if linkHeader == "" || !strings.Contains(linkHeader, `rel="next"`) {
- break
- }
- page++
-
- }
- return allRepos, nil
-}
-
-func PrintGitHubRepos(repos []*gh.Repository) {
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Repo Name", "Owner", "Repo Link", "Private"})
- for _, repo := range repos {
- if *repo.Private {
- green := color.New(color.FgGreen).SprintFunc()
- t.AppendRow([]interface{}{green(*repo.Name), green(*repo.Owner.Login), green(*repo.HTMLURL), green("true")})
- } else {
- t.AppendRow([]interface{}{*repo.Name, *repo.Owner.Login, *repo.HTMLURL, *repo.Private})
- }
- }
- t.Render()
- fmt.Print("\n\n")
-}
-
-func PrintGists(gists []*gh.Gist, showAll bool) {
- privateCount := 0
-
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Gist ID", "Gist Link", "Description", "Private"})
- for _, gist := range gists {
- if gist == nil {
- continue
- }
- gistID := gist.GetID()
- gistLink := gist.GetHTMLURL()
- gistDescription := gist.GetDescription()
- isPublic := gist.GetPublic()
-
- if showAll && isPublic {
- t.AppendRow([]any{gistID, gistLink, gistDescription, "false"})
- } else if !isPublic {
- privateCount++
- green := color.New(color.FgGreen).SprintFunc()
- t.AppendRow([]any{green(gistID), green(gistLink), green(gistDescription), green("true")})
- }
- }
- if showAll && len(gists) == 0 {
- color.Red("[i] No Gist(s) Found\n")
- } else if showAll {
- color.Yellow("[i] Found %v Total Gist(s) (%v private)\n", len(gists), privateCount)
- t.Render()
- } else if privateCount == 0 {
- color.Red("[i] No Private Gist(s) Found\n")
- } else {
- color.Green(fmt.Sprintf("[!] Found %v Private Gist(s)\n", privateCount))
- t.Render()
- }
- fmt.Print("\n\n")
-}
diff --git a/pkg/analyzer/analyzers/github/finegrained/finegrained.go b/pkg/analyzer/analyzers/github/finegrained/finegrained.go
deleted file mode 100644
index d796a3001779..000000000000
--- a/pkg/analyzer/analyzers/github/finegrained/finegrained.go
+++ /dev/null
@@ -1,1372 +0,0 @@
-//go:generate generate_permissions finegrained.yaml finegrained_permissions.go finegrained
-
-package finegrained
-
-import (
- "context"
- "errors"
- "fmt"
- "log"
- "os"
- "strings"
-
- "github.com/fatih/color"
- gh "github.com/google/go-github/v67/github"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
-)
-
-const (
- // Random values for testing
- RANDOM_STRING = "FQ2pR.4voZg-gJfsqYKx_eLDNF_6BYhw8RL__"
- RANDOM_USERNAME = "d" + "ummy" + "acco" + "untgh" + "2024"
- RANDOM_REPO = "te" + "st"
- RANDOM_INTEGER = 4294967289
-)
-
-var ErrInvalid = errors.New("invalid")
-
-var repoPermFuncMap = []func(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error){
- getActionsPermission,
- getAdministrationPermission,
- getCodeScanningAlertsPermission,
- getCodespacesPermission,
- notImplementedRepoPerm, // ToDo: Implement. Docs make this look org-wide...not repo-based?
- getCodespacesMetadataPermission,
- getCodespacesSecretsPermission,
- getCommitStatusesPermission,
- getContentsPermission,
- notImplementedRepoPerm, // ToDo: Only supports orgs. Implement once have an org token.
- getDependabotAlertsPermission,
- getDependabotSecretsPermission,
- getDeploymentsPermission,
- getEnvironmentsPermission,
- getIssuesPermission,
- notImplementedRepoPerm, // Skipped until API better documented
- getMetadataPermission,
- getPagesPermission,
- getPullRequestsPermission,
- getRepoSecurityPermission,
- getSecretScanningPermission,
- getSecretsPermission,
- getVariablesPermission,
- getWebhooksPermission,
- notImplementedRepoPerm, // ToDo: Skipped b/c would require us to create a release (High Risk function)
-}
-
-var acctPermFuncMap = []func(client *gh.Client, user *gh.User) (Permission, error){
- getBlockUserPermission,
- getCodespacesUserPermission,
- getEmailPermission,
- getFollowersPermission,
- getGPGKeysPermission,
- getGistsPermission,
- getGitKeysPermission,
- getLimitsPermission,
- getPlanPermission,
- notImplementedAcctPerm, // Skipped until API better documented
- getProfilePermission,
- getSigningKeysPermission,
- getStarringPermission,
- getWatchingPermission,
-}
-
-// Define your custom formatter function
-func permissionFormatter(key, val any) (string, string) {
- if perm, ok := val.(Permission); ok {
- permStr, err := perm.ToString()
- if err != nil {
- log.Fatal(fmt.Errorf("Error converting permission to string: %v", err))
- }
- var permissionStr string
- switch {
- case strings.Contains(permStr, "read"):
- permissionStr = "READ_ONLY"
- case strings.Contains(permStr, "write"):
- permissionStr = "READ_WRITE"
- default:
- permissionStr = "UNKNOWN"
- }
-
- switch permissionStr {
- case "READ_ONLY":
- yellow := color.New(color.FgYellow).SprintFunc()
- return yellow(key), yellow(permissionStr)
- case "READ_WRITE":
- red := color.New(color.FgGreen).SprintFunc()
- return red(key), red(permissionStr)
- case "UNKNOWN":
- blue := color.New(color.FgBlue).SprintFunc()
- return blue(key), blue(permissionStr)
- }
- }
- return fmt.Sprintf("%v", key), fmt.Sprintf("%v", val)
-}
-
-func notImplementedRepoPerm(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- return NoAccess, nil
-}
-
-// notImplementedAcctPerm is a placeholder function that returns a "NOT_IMPLEMENTED" status when a GitHub account permission is not yet implemented.
-func notImplementedAcctPerm(client *gh.Client, user *gh.User) (Permission, error) {
- return NoAccess, nil
-}
-
-func getMetadataPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // -> GET request to /repos/{owner}/{repo}/collaborators
- _, resp, err := client.Repositories.ListCollaborators(context.Background(), *repo.Owner.Login, *repo.Name, nil)
- if err != nil {
- if resp.StatusCode == 403 {
- return NoAccess, nil
- }
- return Invalid, err
- }
- // If no error, then we have read access
-
- return MetadataRead, nil
-}
-
-func getActionsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- if *repo.Private {
- // Risk: Extremely Low
- // -> GET request to /repos/{owner}/{repo}/actions/artifacts
- _, resp, err := client.Actions.ListArtifacts(context.Background(), *repo.Owner.Login, *repo.Name, nil)
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very, very low.
- // -> Unless the user has a workflow file named (see RANDOM_STRING above), this will always return 404 for users with READ_WRITE permissions.
- // -> POST request to /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches
- resp, err = client.Actions.CreateWorkflowDispatchEventByFileName(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, gh.CreateWorkflowDispatchEventRequest{})
- switch resp.StatusCode {
- case 403:
- return ActionsRead, nil
- case 404:
- return ActionsWrite, nil
- case 200:
- log.Fatal("This shouldn't print. We are enabling a workflow based on a random string " + RANDOM_STRING + ", which most likely doesn't exist.")
- return ActionsWrite, nil
- default:
- return Invalid, err
- }
- } else {
- // Will only land here if already tested one public repo and got a 403.
- if currentAccess == NoAccess {
- return NoAccess, nil
- }
- // Risk: Very, very low.
- // -> Unless the user has a workflow file named (see RANDOM_STRING above), this will always return 404 for users with READ_WRITE permissions.
- // -> POST request to /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches
- resp, err := client.Actions.CreateWorkflowDispatchEventByFileName(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, gh.CreateWorkflowDispatchEventRequest{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 404:
- return ActionsWrite, nil
- case 200:
- log.Fatal("This shouldn't print. We are enabling a workflow based on a random string " + RANDOM_STRING + ", which most likely doesn't exist.")
- return ActionsWrite, nil
- default:
- return Invalid, err
- }
- }
-}
-
-// Continue with the other functions using the same pattern...
-
-func getAdministrationPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // -> GET request to /repos/{owner}/{repo}/actions/permissions
- _, resp, err := client.Repositories.GetActionsPermissions(context.Background(), *repo.Owner.Login, *repo.Name)
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Extremely Low
- // -> GET request to /repos/{owner}/{repo}/rulesets/rule-suites
- req, err := client.NewRequest("GET", "https://api.github.com/repos/"+*repo.Owner.Login+"/"+*repo.Name+"/rulesets/rule-suites", nil)
- if err != nil {
- return Invalid, err
- }
- resp, err = client.Do(context.Background(), req, nil)
- switch resp.StatusCode {
- case 403:
- return AdministrationRead, nil
- case 200:
- return AdministrationWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getCodeScanningAlertsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // -> GET request to /repos/{owner}/{repo}/code-scanning/alerts
- _, resp, err := client.CodeScanning.ListAlertsForRepo(context.Background(), *repo.Owner.Login, *repo.Name, nil)
- defer resp.Body.Close()
-
- switch {
- case resp.StatusCode == 403:
- return NoAccess, nil
- case resp.StatusCode == 404:
- break
- case resp.StatusCode >= 200 && resp.StatusCode <= 299:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very Low
- // -> Even if user had an alert with the number (see RANDOM_INTEGER above), this should error 422 due to the nil value passed in.
- // -> PATCH request to /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}
- _, resp, err = client.CodeScanning.UpdateAlert(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_INTEGER, nil)
- switch resp.StatusCode {
- case 403:
- return CodeScanningAlertsRead, nil
- case 422:
- return CodeScanningAlertsWrite, nil
- case 200:
- log.Fatal("This should never happen. We are updating an alert with nil which should be an invalid request.")
- return CodeScanningAlertsWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getCodespacesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // GET request to /repos/{owner}/{repo}/codespaces
- _, resp, err := client.Codespaces.ListInRepo(context.Background(), *repo.Owner.Login, *repo.Name, nil)
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Extremely Low
- // GET request to /repos/{owner}/{repo}/codespaces/permissions_check
- req, err := client.NewRequest("GET", "https://api.github.com/repos/"+*repo.Owner.Login+"/"+*repo.Name+"/codespaces/permissions_check", nil)
- if err != nil {
- return Invalid, err
- }
- resp, err = client.Do(context.Background(), req, nil)
- switch resp.StatusCode {
- case 403:
- return CodespacesRead, nil
- case 422:
- return CodespacesWrite, nil
- case 200:
- return CodespacesWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getCodespacesMetadataPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // GET request to /repos/{owner}/{repo}/codespaces/machines
- req, err := client.NewRequest("GET", "https://api.github.com/repos/"+*repo.Owner.Login+"/"+*repo.Name+"/codespaces/machines", nil)
- if err != nil {
- return Invalid, err
- }
- resp, err := client.Do(context.Background(), req, nil)
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- return CodespacesMetadataRead, nil
- default:
- return Invalid, err
- }
-}
-
-func getCodespacesSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // GET request to /repos/{owner}/{repo}/codespaces/secrets for non-existent secret
- _, resp, err := client.Codespaces.GetRepoSecret(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING)
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 404:
- return CodespacesSecretsWrite, nil
- case 200:
- return CodespacesSecretsWrite, nil
- default:
- return Invalid, err
- }
-}
-
-// getCommitStatusesPermission will check if we have access to commit statuses for a given repo.
-// By default, we have read-only access to commit statuses for all public repos. If only public repos exist under
-// this key's permissions, then they best we can hope for us a READ_WRITE status or an UNKNOWN status.
-// If a private repo exists, then we can check for READ_ONLY, READ_WRITE and NO_ACCESS.
-func getCommitStatusesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- if *repo.Private {
- // Risk: Extremely Low
- // GET request to /repos/{owner}/{repo}/commits/{commit_sha}/statuses
- _, resp, err := client.Repositories.ListStatuses(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, nil)
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 404:
- break
- default:
- return Invalid, err
- }
- // At this point we have read access
-
- // Risk: Extremely Low
- // -> We're POSTing a commit status to a commit that cannot exist. This should always return 422 if valid access.
- // POST request to /repos/{owner}/{repo}/statuses/{commit_sha}
- _, resp, err = client.Repositories.CreateStatus(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.RepoStatus{})
- switch resp.StatusCode {
- case 403:
- return CommitStatusesRead, nil
- case 422:
- return CommitStatusesWrite, nil
- default:
- return Invalid, err
- }
- } else {
- // Will only land here if already tested one public repo and got a 403.
- if currentAccess == NoAccess {
- return NoAccess, nil
- }
- // Risk: Extremely Low
- // -> We're POSTing a commit status to a commit that cannot exist. This should always return 422 if valid access.
- // POST request to /repos/{owner}/{repo}/statuses/{commit_sha}
- _, resp, err := client.Repositories.CreateStatus(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.RepoStatus{})
- switch resp.StatusCode {
- case 403:
- // All we know is we don't have READ_WRITE
- return NoAccess, nil
- case 422:
- return CommitStatusesWrite, nil
- default:
- return Invalid, err
- }
- }
-}
-
-// getContentsPermission will check if we have access to the contents of a given repo.
-// By default, we have read-only access to the contents of all public repos. If only public repos exist under
-// this key's permissions, then they best we can hope for us a READ_WRITE status or an UNKNOWN status.
-// If a private repo exists, then we can check for READ_ONLY, READ_WRITE and NO_ACCESS.
-func getContentsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- if *repo.Private {
- // Risk: Extremely Low
- // GET request to /repos/{owner}/{repo}/commits
- _, resp, err := client.Repositories.ListCommits(context.Background(), *repo.Owner.Login, *repo.Name, &gh.CommitsListOptions{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- break
- case 409:
- break
- default:
- return Invalid, err
- }
- // At this point we have read access
-
- // Risk: Low-Medium
- // -> We're creating a file with an invalid payload. Worst case is a file with a random string and no content is created. But this should never happen.
- // PUT /repos/{owner}/{repo}/contents/{path}
- _, resp, err = client.Repositories.CreateFile(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.RepositoryContentFileOptions{})
- switch resp.StatusCode {
- case 403:
- return ContentsRead, nil
- case 200:
- log.Fatal("This should never happen. We are creating a file with an invalid payload.")
- return ContentsWrite, nil
- case 400, 422:
- return ContentsWrite, nil
- default:
- return Invalid, err
- }
- } else {
- // Will only land here if already tested one public repo and got a 403.
- if currentAccess == NoAccess {
- return NoAccess, nil
- }
- // Risk: Low-Medium
- // -> We're creating a file with an invalid payload. Worst case is a file with a random string and no content is created. But this should never happen.
- // PUT /repos/{owner}/{repo}/contents/{path}
- _, resp, err := client.Repositories.CreateFile(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.RepositoryContentFileOptions{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- panic("This should never happen. We are creating a file with an invalid payload.")
- case 400, 422:
- return ContentsWrite, nil
- default:
- return Invalid, err
- }
- }
-}
-
-func getDependabotAlertsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/dependabot/alerts
- _, resp, err := client.Dependabot.ListRepoAlerts(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListAlertsOptions{})
- defer resp.Body.Close()
-
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // PATCH /repos/{owner}/{repo}/dependabot/alerts/{alert_number}
- _, resp, err = client.Dependabot.UpdateAlert(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_INTEGER, nil)
- switch resp.StatusCode {
- case 403:
- return DependabotAlertsRead, nil
- case 422, 404:
- return DependabotAlertsWrite, nil
- case 200:
- log.Fatal("This should never happen. We are updating an alert with nil which should be an invalid request.")
- return DependabotAlertsWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getDependabotSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/dependabot/secrets
- _, resp, err := client.Dependabot.ListRepoSecrets(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very Low
- // -> We're "creating" a secret with an invalid payload. Even if we did, the name would be (see RANDOM_STRING above) and the value would be nil.
- // PUT /repos/{owner}/{repo}/dependabot/secrets/{secret_name}
- resp, err = client.Dependabot.CreateOrUpdateRepoSecret(context.Background(), *repo.Owner.Login, *repo.Name, &gh.DependabotEncryptedSecret{Name: RANDOM_STRING})
- switch resp.StatusCode {
- case 403:
- return DependabotSecretsRead, nil
- case 422:
- return DependabotSecretsWrite, nil
- case 201, 204:
- log.Fatal("This should never happen. We are creating a secret with an invalid payload.")
- return DependabotSecretsWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getDeploymentsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/deployments
- _, resp, err := client.Repositories.ListDeployments(context.Background(), *repo.Owner.Login, *repo.Name, &gh.DeploymentsListOptions{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very Low
- // -> We're creating a deployment with an invalid payload. Even if we did, the name would be (see RANDOM_STRING above) and the value would be nil.
- // POST /repos/{owner}/{repo}/deployments/{deployment_id}/statuses
- _, resp, err = client.Repositories.CreateDeployment(context.Background(), *repo.Owner.Login, *repo.Name, &gh.DeploymentRequest{})
- switch resp.StatusCode {
- case 403:
- return DeploymentsRead, nil
- case 409, 422:
- return DeploymentsWrite, nil
- case 201, 202:
- log.Fatal("This should never happen. We are creating a deployment with an invalid payload.")
- return DeploymentsWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getEnvironmentsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/environments
- envResp, resp, _ := client.Repositories.ListEnvironments(context.Background(), *repo.Owner.Login, *repo.Name, &gh.EnvironmentListOptions{})
- if resp.StatusCode != 200 {
- return NoAccess, nil
- }
- // If no environments exist, then we return UNKNOWN
- if len(envResp.Environments) == 0 {
- return NoAccess, nil
- }
-
- // Risk: Extremely Low
- // GET /repositories/{repository_id}/environments/{environment_name}/variables
- _, resp, _ = client.Actions.ListEnvVariables(context.Background(), *repo.Owner.Login, *repo.Name, *envResp.Environments[0].Name, &gh.ListOptions{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, nil
- }
-
- // Risk: Very Low
- // -> We're updating an environment variable with an invalid payload. Even if we did, the name would be (see RANDOM_STRING above) and the value would be nil.
- // PATCH /repositories/{repository_id}/environments/{environment_name}/variables/{variable_name}
- resp, err := client.Actions.UpdateEnvVariable(context.Background(), *repo.Owner.Login, *repo.Name, *envResp.Environments[0].Name, &gh.ActionsVariable{Name: RANDOM_STRING})
- switch resp.StatusCode {
- case 403:
- return EnvironmentsRead, nil
- case 422:
- return EnvironmentsWrite, nil
- case 200:
- log.Fatal("This should never happen. We are updating an environment variable with an invalid payload.")
- return EnvironmentsWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getIssuesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
-
- if *repo.Private {
-
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/issues
- _, resp, err := client.Issues.ListByRepo(context.Background(), *repo.Owner.Login, *repo.Name, &gh.IssueListByRepoOptions{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200, 301:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very Low
- // -> We're editing an issue label that does not exist. Even if we did, the name would be (see RANDOM_STRING above).
- // PATCH /repos/{owner}/{repo}/labels/{name}
- _, resp, err = client.Issues.EditLabel(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.Label{})
- switch resp.StatusCode {
- case 403:
- return IssuesRead, nil
- case 404:
- return IssuesWrite, nil
- case 200:
- log.Fatal("This should never happen. We are editing a label with an invalid payload.")
- return IssuesWrite, nil
- default:
- return Invalid, err
- }
- } else {
- // Will only land here if already tested one public repo and got a 403.
- if currentAccess == NoAccess {
- return NoAccess, nil
- }
- // Risk: Very Low
- // -> We're editing an issue label that does not exist. Even if we did, the name would be (see RANDOM_STRING above).
- // PATCH /repos/{owner}/{repo}/labels/{name}
- _, resp, err := client.Issues.EditLabel(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.Label{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 404:
- return IssuesWrite, nil
- case 200:
- log.Fatal("This should never happen. We are editing a label with an invalid payload.")
- return IssuesWrite, nil
- default:
- return Invalid, err
- }
- }
-}
-
-func getPagesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- if *repo.Private {
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/pages
- _, resp, err := client.Repositories.GetPagesInfo(context.Background(), *repo.Owner.Login, *repo.Name)
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200, 404:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very Low
- // -> We're cancelling a GitHub Pages deployment that does not exist (see RANDOM_STRING above).
- // POST /repos/{owner}/{repo}/pages/deployments/{deployment_id}/cancel
- req, err := client.NewRequest("POST", "https://api.github.com/repos/"+*repo.Owner.Login+"/"+*repo.Name+"/pages/deployments/"+RANDOM_STRING+"/cancel", nil)
- if err != nil {
- return Invalid, err
- }
- resp, err = client.Do(context.Background(), req, nil)
- switch resp.StatusCode {
- case 403:
- return PagesRead, nil
- case 404:
- return PagesWrite, nil
- case 200:
- log.Fatal("This should never happen. We are cancelling a deployment with an invalid ID.")
- return PagesWrite, nil
- default:
- return Invalid, err
- }
- } else {
- // Will only land here if already tested one public repo and got a 403.
- if currentAccess == NoAccess {
- return NoAccess, nil
- }
- // Risk: Very Low
- // -> We're cancelling a GitHub Pages deployment that does not exist (see RANDOM_STRING above).
- // POST /repos/{owner}/{repo}/pages/deployments/{deployment_id}/cancel
- req, err := client.NewRequest("POST", "https://api.github.com/repos/"+*repo.Owner.Login+"/"+*repo.Name+"/pages/deployments/"+RANDOM_STRING+"/cancel", nil)
- if err != nil {
- return Invalid, err
- }
- resp, err := client.Do(context.Background(), req, nil)
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 404:
- return PagesWrite, nil
- case 200:
- log.Fatal("This should never happen. We are cancelling a deployment with an invalid ID.")
- return PagesWrite, nil
- default:
- return Invalid, err
- }
- }
-}
-
-func getPullRequestsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- if *repo.Private {
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/pulls
- _, resp, err := client.PullRequests.List(context.Background(), *repo.Owner.Login, *repo.Name, &gh.PullRequestListOptions{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very Low
- // -> We're creating a pull request with an invalid payload.
- // POST /repos/{owner}/{repo}/pulls
- _, resp, err = client.PullRequests.Create(context.Background(), *repo.Owner.Login, *repo.Name, &gh.NewPullRequest{})
- switch resp.StatusCode {
- case 403:
- return PullRequestsRead, nil
- case 422:
- return PullRequestsWrite, nil
- case 200:
- log.Fatal("This should never happen. We are creating a pull request with an invalid payload.")
- return PullRequestsWrite, nil
- default:
- return Invalid, err
- }
- } else {
- // Will only land here if already tested one public repo and got a 403.
- if currentAccess == NoAccess {
- return NoAccess, nil
- }
- // Risk: Very Low
- // -> We're creating a pull request with an invalid payload.
- // POST /repos/{owner}/{repo}/pulls
- _, resp, err := client.PullRequests.Create(context.Background(), *repo.Owner.Login, *repo.Name, &gh.NewPullRequest{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 422:
- return PullRequestsWrite, nil
- case 200:
- log.Fatal("This should never happen. We are creating a pull request with an invalid payload.")
- return PullRequestsWrite, nil
- default:
- return Invalid, err
- }
- }
-}
-
-func getRepoSecurityPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
-
- if *repo.Private {
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/security-advisories
- _, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisories(context.Background(), *repo.Owner.Login, *repo.Name, nil)
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very Low
- // -> We're creating a security advisory with an invalid payload.
- // POST /repos/{owner}/{repo}/security-advisories
- req, err := client.NewRequest("POST", "https://api.github.com/repos/"+*repo.Owner.Login+"/"+*repo.Name+"/security-advisories", nil)
- if err != nil {
- return Invalid, err
- }
- resp, err = client.Do(context.Background(), req, nil)
- switch resp.StatusCode {
- case 403:
- return RepoSecurityRead, nil
- case 422:
- return RepoSecurityWrite, nil
- case 200:
- log.Fatal("This should never happen. We are creating a security advisory with an invalid payload.")
- return RepoSecurityWrite, nil
- default:
- return Invalid, err
- }
- } else {
- // Will only land here if already tested one public repo and got a 403.
- if currentAccess == NoAccess {
- return NoAccess, nil
- }
- // Risk: Very Low
- // -> We're creating a security advisory with an invalid payload.
- // POST /repos/{owner}/{repo}/security-advisories
- req, err := client.NewRequest("POST", "https://api.github.com/repos/"+*repo.Owner.Login+"/"+*repo.Name+"/security-advisories", nil)
- if err != nil {
- return Invalid, err
- }
- resp, err := client.Do(context.Background(), req, nil)
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 422:
- return RepoSecurityWrite, nil
- case 200:
- log.Fatal("This should never happen. We are creating a security advisory with an invalid payload.")
- return RepoSecurityWrite, nil
- default:
- return Invalid, err
- }
- }
-}
-
-func getSecretScanningPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/secret-scanning/alerts
- _, resp, err := client.SecretScanning.ListAlertsForRepo(context.Background(), *repo.Owner.Login, *repo.Name, nil)
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200, 404:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very Low
- // -> We're updating a secret scanning alert for an alert that doesn't exist.
- // POST /repos/{owner}/{repo}/secret-scanning/alerts
- _, resp, err = client.SecretScanning.UpdateAlert(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_INTEGER, &gh.SecretScanningAlertUpdateOptions{})
- switch resp.StatusCode {
- case 403:
- return SecretScanningRead, nil
- case 404, 422:
- return SecretScanningWrite, nil
- case 200:
- log.Fatal("This should never happen. We are updating a secret scanning alert that doesn't exist.")
- return SecretScanningWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/actions/secrets
- _, resp, err := client.Actions.ListRepoSecrets(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very Low
- // -> We're creating a secret with an invalid payload.
- // PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}
- resp, err = client.Actions.CreateOrUpdateRepoSecret(context.Background(), *repo.Owner.Login, *repo.Name, &gh.EncryptedSecret{Name: RANDOM_STRING})
- switch resp.StatusCode {
- case 403:
- return SecretsRead, nil
- case 422:
- return SecretsWrite, nil
- case 201, 204:
- log.Fatal("This should never happen. We are creating a secret with an invalid payload.")
- return SecretsWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getVariablesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/actions/variables
- _, resp, err := client.Actions.ListRepoVariables(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very Low
- // -> We're updating a variable that doesn't exist with an invalid payload.
- // PATCH /repos/{owner}/{repo}/actions/variables/{name}
- resp, err = client.Actions.UpdateRepoVariable(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ActionsVariable{Name: RANDOM_STRING})
- switch resp.StatusCode {
- case 403:
- return VariablesRead, nil
- case 422:
- return VariablesWrite, nil
- case 201, 204:
- log.Fatal("This should never happen. We are patching a variable with an invalid payload and no name.")
- return VariablesWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getWebhooksPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
- // Risk: Extremely Low
- // GET /repos/{owner}/{repo}/hooks
- _, resp, err := client.Repositories.ListHooks(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Very Low
- // -> We're updating a webhook that doesn't exist with an invalid payload.
- // PATCH /repos/{owner}/{repo}/hooks/{hook_id}
- _, resp, err = client.Repositories.EditHook(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_INTEGER, &gh.Hook{})
- switch resp.StatusCode {
- case 403:
- return WebhooksRead, nil
- case 404:
- return WebhooksWrite, nil
- case 200:
- log.Fatal("This should never happen. We are updating a webhook with an invalid payload.")
- return WebhooksWrite, nil
- default:
- return Invalid, err
- }
-}
-
-// analyzeRepositoryPermissions will analyze the fine-grained permissions of a given permission type and return the access level.
-// This function is needed b/c in some cases a token could have permissions that are only enabled on specific repos.
-// If we only checked one repo, we wouldn't be able to tell if the token has access to a specific permission type.
-// Ex: "Code scanning alerts" must be enabled to tell if we have that permission.
-func analyzeRepositoryPermissions(client *gh.Client, repos []*gh.Repository) ([]Permission, error) {
- perms := make([]Permission, len(repoPermFuncMap))
- for _, repo := range repos {
- for i, permFunc := range repoPermFuncMap {
- access, err := permFunc(client, repo, perms[i])
- if err != nil || access == Invalid {
- // TODO: Log error.
- continue
- }
- if perms[i] == Invalid || perms[i] == NoAccess {
- perms[i] = access
- }
- }
- }
- return perms, nil
-}
-
-func getBlockUserPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Risk: Extremely Low
- // -> GET request to /user/blocks
- _, resp, err := client.Users.ListBlockedUsers(context.Background(), nil)
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Extremely Low
- // -> PUT request to /user/blocks/{username}
- // -> We're blocking a user that doesn't exist. See RANDOM_STRING above.
- resp, err = client.Users.BlockUser(context.Background(), RANDOM_STRING)
- switch resp.StatusCode {
- case 403:
- return BlockUserRead, nil
- case 404:
- return BlockUserWrite, nil
- case 204:
- log.Fatal("This should never happen. We are blocking a user that doesn't exist.")
- return BlockUserWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getCodespacesUserPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Risk: Extremely Low
- // GET request to /user/codespaces/secrets
- _, resp, err := client.Codespaces.ListUserSecrets(context.Background(), nil)
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Low
- // PUT request to /user/codespaces/secrets/{secret_name}
- // Payload is invalid, so it shouldn't actually post.
- resp, err = client.Codespaces.CreateOrUpdateUserSecret(context.Background(), &gh.EncryptedSecret{Name: RANDOM_STRING})
- switch resp.StatusCode {
- case 403:
- return CodespaceUserSecretsRead, nil
- case 422:
- return CodespaceUserSecretsWrite, nil
- case 201, 204:
- log.Fatal("This should never happen. We are creating a user secret with an invalid payload.")
- return CodespaceUserSecretsWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getEmailPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Risk: Extremely Low
- // GET request to /user/emails
- _, resp, err := client.Users.ListEmails(context.Background(), nil)
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Low
- // POST request to /user/emails/visibility
- _, resp, err = client.Users.SetEmailVisibility(context.Background(), RANDOM_STRING)
- switch resp.StatusCode {
- case 403, 404:
- return EmailRead, nil
- case 422:
- return EmailWrite, nil
- case 201:
- log.Fatal("This should never happen. We are setting email visibility with an invalid payload.")
- return EmailWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getFollowersPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Risk: Extremely Low
- // GET request to /user/followers
- _, resp, err := client.Users.ListFollowers(context.Background(), "", nil)
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Low - Medium
- // DELETE request to /user/followers/{username}
- // For the username value, we need to use a real username. So there is a super small chance that someone following
- // an account for RANDOM_USERNAME value will then no longer follow that account.
- // But we're using an account created specifically for this purpose with no activity.
- resp, err = client.Users.Unfollow(context.Background(), RANDOM_USERNAME)
- switch resp.StatusCode {
- case 403, 404:
- return FollowersRead, nil
- case 204:
- return FollowersWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getGPGKeysPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Risk: Extremely Low
- // GET request to /user/gpg_keys
- _, resp, err := client.Users.ListGPGKeys(context.Background(), "", nil)
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Low - Medium
- // POST request to /user/gpg_keys
- // Payload is invalid, so it shouldn't actually post.
- _, resp, err = client.Users.CreateGPGKey(context.Background(), RANDOM_STRING)
- switch resp.StatusCode {
- case 403:
- return GpgKeysRead, nil
- case 422:
- return GpgKeysWrite, nil
- case 200, 201, 204:
- log.Fatal("This should never happen. We are creating a GPG key with an invalid payload.")
- return GpgKeysWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getGistsPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Risk: Low - Medium
- // POST request to /gists
- // Payload is invalid, so it shouldn't actually post.
- _, resp, err := client.Gists.Create(context.Background(), &gh.Gist{})
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 422:
- return GistsWrite, nil
- case 200, 201, 204:
- log.Fatal("This should never happen. We are creating a Gist with an invalid payload.")
- return GistsWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getGitKeysPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Risk: Extremely Low
- // GET request to /user/keys
- _, resp, err := client.Users.ListKeys(context.Background(), "", nil)
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Low - Medium
- // POST request to /user/keys
- // Payload is invalid, so it shouldn't actually post.
- _, resp, err = client.Users.CreateKey(context.Background(), &gh.Key{})
- switch resp.StatusCode {
- case 403:
- return GitKeysRead, nil
- case 422:
- return GitKeysWrite, nil
- case 200, 201, 204:
- log.Fatal("This should never happen. We are creating a key with an invalid payload.")
- return GitKeysWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getLimitsPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Risk: Extremely Low
- // GET request to /user/interaction-limits
- req, err := client.NewRequest("GET", "https://api.github.com/user/interaction-limits", nil)
- if err != nil {
- return Invalid, err
- }
- resp, err := client.Do(context.Background(), req, nil)
- switch resp.StatusCode {
- case 403:
- return NoAccess, nil
- case 200, 204:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Low
- // PUT request to /user/interaction-limits
- // Payload is invalid, so it shouldn't actually post.
- req, err = client.NewRequest("PUT", "https://api.github.com/user/interaction-limits", nil)
- if err != nil {
- return Invalid, err
- }
- resp, err = client.Do(context.Background(), req, nil)
- switch resp.StatusCode {
- case 403:
- return LimitsRead, nil
- case 422:
- return LimitsWrite, nil
- case 200, 204:
- log.Fatal("This should never happen. We are setting interaction limits with an invalid payload.")
- return LimitsWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getPlanPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Risk: Extremely Low
- // GET request to /user/{username}/settings/billing/actions
- _, resp, err := client.Billing.GetActionsBillingUser(context.Background(), *user.Login)
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 200:
- return PlanRead, nil
- default:
- return Invalid, err
- }
-}
-
-func getProfilePermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Risk: Low
- // POST request to /user/social_accounts
- // Payload is invalid, so it shouldn't actually patch.
- req, err := client.NewRequest("POST", "https://api.github.com/user/social_accounts", nil)
- if err != nil {
- return Invalid, err
- }
- resp, err := client.Do(context.Background(), req, nil)
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 422:
- return ProfileWrite, nil
- case 200, 201, 204:
- log.Fatal("This should never happen. We are creating a social account with an invalid payload.")
- return ProfileWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getSigningKeysPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Risk: Extremely Low
- // GET request to /user/ssh_signing_keys
- _, resp, err := client.Users.ListSSHSigningKeys(context.Background(), "", nil)
- switch resp.StatusCode {
- case 403, 404:
- return NoAccess, nil
- case 200:
- break
- default:
- return Invalid, err
- }
-
- // Risk: Low - Medium
- // POST request to /user/ssh_signing_keys
- // Payload is invalid, so it shouldn't actually post.
- _, resp, err = client.Users.CreateSSHSigningKey(context.Background(), &gh.Key{})
- switch resp.StatusCode {
- case 403:
- return SigningKeysRead, nil
- case 422:
- return SigningKeysWrite, nil
- case 200, 201, 204:
- log.Fatal("This should never happen. We are creating a SSH key with an invalid payload.")
- return SigningKeysWrite, nil
- default:
- return Invalid, err
- }
-}
-
-func getStarringPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Note: We can't test READ_WRITE b/c Unstar() isn't working even with READ_WRITE permissions.
- // Note: GET /user/starred returns the same results regardless of permissions
- // but since all have the same access, we'll call it READ_ONLY for now.
- return StarringRead, nil
-}
-
-func getWatchingPermission(client *gh.Client, user *gh.User) (Permission, error) {
- // Note: GET /user/subscriptions returns the same results regardless of permissions
- // but since all have the same access, we'll call it READ_ONLY for now.
- return WatchingRead, nil
-}
-
-func analyzeUserPermissions(client *gh.Client, user *gh.User) ([]Permission, error) {
- perms := []Permission{}
- for _, permFunc := range acctPermFuncMap {
- access, err := permFunc(client, user)
- if err != nil {
- // TODO: Log error.
- continue
- }
- perms = append(perms, access)
- }
-
- return perms, nil
-}
-
-func AnalyzeFineGrainedToken(client *gh.Client, meta *common.TokenMetadata, shallowCheck bool) (*common.SecretInfo, error) {
- allRepos, err := common.GetAllReposForUser(client)
- if err != nil {
- return nil, err
- }
-
- allGists, err := common.GetAllGistsForUser(client)
- if err != nil {
- return nil, err
- }
- accessibleRepos := make([]*gh.Repository, 0)
- for _, repo := range allRepos {
- perm, err := getMetadataPermission(client, repo, Invalid)
- if err != nil {
- // TODO: Log error.
- continue
- }
- if perm != Invalid {
- accessibleRepos = append(accessibleRepos, repo)
- }
- }
-
- repoAccessMap := []Permission{}
- userAccessMap := []Permission{}
-
- if !shallowCheck {
- // Check our access
- perms, err := analyzeRepositoryPermissions(client, accessibleRepos)
- if err != nil {
- return nil, err
- }
- for _, perm := range perms {
- if perm != Invalid && perm != NoAccess {
- repoAccessMap = append(repoAccessMap, perm)
- }
- }
-
- perms, err = analyzeUserPermissions(client, meta.User)
- if err != nil {
- return nil, err
- }
- for _, perm := range perms {
- if perm != Invalid && perm != NoAccess {
- userAccessMap = append(userAccessMap, perm)
- }
- }
- }
-
- return &common.SecretInfo{
- Metadata: meta,
- Repos: allRepos,
- Gists: allGists,
- AccessibleRepos: accessibleRepos,
- RepoAccessMap: repoAccessMap,
- UserAccessMap: userAccessMap,
- }, nil
-}
-
-func PrintFineGrainedToken(cfg *config.Config, info *common.SecretInfo) {
- if len(info.AccessibleRepos) == 0 {
- // If no repos are accessible, then we only have read access to public repos
- color.Red("[!] Repository Access: Public Repositories (read-only)\n")
- } else {
- // Print out the repos the token can access
- color.Green(fmt.Sprintf("Found %v", len(info.AccessibleRepos)) + " Accessible Repositor(ies) \n")
- common.PrintGitHubRepos(info.AccessibleRepos)
-
- // Print out the access map
- perms, ok := info.RepoAccessMap.([]Permission)
- if !ok {
- panic("Repo Access Map is not of type Permission")
- }
- printFineGrainedPermissions(perms, cfg.ShowAll, true)
- }
-
- perms, ok := info.UserAccessMap.([]Permission)
- if !ok {
- panic("Repo Access Map is not of type Permission")
- }
-
- printFineGrainedPermissions(perms, cfg.ShowAll, false)
- common.PrintGists(info.Gists, cfg.ShowAll)
-}
-
-func printFineGrainedPermissions(accessMap []Permission, showAll bool, repoPermissions bool) {
- permissionCount := 0
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission Type", "Permission" /* Add more column headers if needed */})
-
- for _, perm := range accessMap {
- permStr, _ := perm.ToString()
- if perm == Invalid {
- // don't change permissionCount
- } else {
- permissionCount++
- }
- if !showAll && perm == Invalid {
- continue
- } else {
- k, v := permissionFormatter(permStr, perm)
- t.AppendRow([]any{k, v})
- }
- }
- var permissionType string
- if repoPermissions {
- permissionType = "Repositor(ies)"
- } else {
- permissionType = "User Account"
- }
- if permissionCount == 0 && !showAll {
- color.Red("No Permissions Found for the %v above\n\n", permissionType)
- return
- } else if permissionCount == 0 {
- color.Red("Found No Permissions for the %v above\n", permissionType)
- } else {
- color.Green(fmt.Sprintf("Found %v Permission(s) for the %v above\n", permissionCount, permissionType))
- }
- t.Render()
- fmt.Print("\n\n")
-}
diff --git a/pkg/analyzer/analyzers/github/finegrained/finegrained.yaml b/pkg/analyzer/analyzers/github/finegrained/finegrained.yaml
deleted file mode 100644
index 2908601c7327..000000000000
--- a/pkg/analyzer/analyzers/github/finegrained/finegrained.yaml
+++ /dev/null
@@ -1,81 +0,0 @@
-# Please generate a yaml list of all of the strings permission_name:access_level for all of the permissions and access levels that can be emitted from the test functions. The strings should be lower snake case with a colon joining the permission name and access level. The only access levels I want are "read" and "write"
-permissions:
- - no_access
- - actions:read
- - actions:write
- - administration:read
- - administration:write
- - code_scanning_alerts:read
- - code_scanning_alerts:write
- - codespaces:read
- - codespaces:write
- - codespaces_lifecycle:read
- - codespaces_lifecycle:write
- - codespaces_metadata:read
- - codespaces_metadata:write
- - codespaces_secrets:read
- - codespaces_secrets:write
- - commit_statuses:read
- - commit_statuses:write
- - contents:read
- - contents:write
- - custom_properties:read
- - custom_properties:write
- - dependabot_alerts:read
- - dependabot_alerts:write
- - dependabot_secrets:read
- - dependabot_secrets:write
- - deployments:read
- - deployments:write
- - environments:read
- - environments:write
- - issues:read
- - issues:write
- - merge_queues:read
- - merge_queues:write
- - metadata:read
- - metadata:write
- - pages:read
- - pages:write
- - pull_requests:read
- - pull_requests:write
- - repo_security:read
- - repo_security:write
- - secret_scanning:read
- - secret_scanning:write
- - secrets:read
- - secrets:write
- - variables:read
- - variables:write
- - webhooks:read
- - webhooks:write
- - workflows:read
- - workflows:write
- - block_user:read
- - block_user:write
- - codespace_user_secrets:read
- - codespace_user_secrets:write
- - email:read
- - email:write
- - followers:read
- - followers:write
- - gpg_keys:read
- - gpg_keys:write
- - gists:read
- - gists:write
- - git_keys:read
- - git_keys:write
- - limits:read
- - limits:write
- - plan:read
- - plan:write
- - private_invites:read
- - private_invites:write
- - profile:read
- - profile:write
- - signing_keys:read
- - signing_keys:write
- - starring:read
- - starring:write
- - watching:read
- - watching:write
diff --git a/pkg/analyzer/analyzers/github/finegrained/finegrained_permissions.go b/pkg/analyzer/analyzers/github/finegrained/finegrained_permissions.go
deleted file mode 100644
index 75ff96104a0f..000000000000
--- a/pkg/analyzer/analyzers/github/finegrained/finegrained_permissions.go
+++ /dev/null
@@ -1,451 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package finegrained
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- NoAccess Permission = iota
- ActionsRead Permission = iota
- ActionsWrite Permission = iota
- AdministrationRead Permission = iota
- AdministrationWrite Permission = iota
- CodeScanningAlertsRead Permission = iota
- CodeScanningAlertsWrite Permission = iota
- CodespacesRead Permission = iota
- CodespacesWrite Permission = iota
- CodespacesLifecycleRead Permission = iota
- CodespacesLifecycleWrite Permission = iota
- CodespacesMetadataRead Permission = iota
- CodespacesMetadataWrite Permission = iota
- CodespacesSecretsRead Permission = iota
- CodespacesSecretsWrite Permission = iota
- CommitStatusesRead Permission = iota
- CommitStatusesWrite Permission = iota
- ContentsRead Permission = iota
- ContentsWrite Permission = iota
- CustomPropertiesRead Permission = iota
- CustomPropertiesWrite Permission = iota
- DependabotAlertsRead Permission = iota
- DependabotAlertsWrite Permission = iota
- DependabotSecretsRead Permission = iota
- DependabotSecretsWrite Permission = iota
- DeploymentsRead Permission = iota
- DeploymentsWrite Permission = iota
- EnvironmentsRead Permission = iota
- EnvironmentsWrite Permission = iota
- IssuesRead Permission = iota
- IssuesWrite Permission = iota
- MergeQueuesRead Permission = iota
- MergeQueuesWrite Permission = iota
- MetadataRead Permission = iota
- MetadataWrite Permission = iota
- PagesRead Permission = iota
- PagesWrite Permission = iota
- PullRequestsRead Permission = iota
- PullRequestsWrite Permission = iota
- RepoSecurityRead Permission = iota
- RepoSecurityWrite Permission = iota
- SecretScanningRead Permission = iota
- SecretScanningWrite Permission = iota
- SecretsRead Permission = iota
- SecretsWrite Permission = iota
- VariablesRead Permission = iota
- VariablesWrite Permission = iota
- WebhooksRead Permission = iota
- WebhooksWrite Permission = iota
- WorkflowsRead Permission = iota
- WorkflowsWrite Permission = iota
- BlockUserRead Permission = iota
- BlockUserWrite Permission = iota
- CodespaceUserSecretsRead Permission = iota
- CodespaceUserSecretsWrite Permission = iota
- EmailRead Permission = iota
- EmailWrite Permission = iota
- FollowersRead Permission = iota
- FollowersWrite Permission = iota
- GpgKeysRead Permission = iota
- GpgKeysWrite Permission = iota
- GistsRead Permission = iota
- GistsWrite Permission = iota
- GitKeysRead Permission = iota
- GitKeysWrite Permission = iota
- LimitsRead Permission = iota
- LimitsWrite Permission = iota
- PlanRead Permission = iota
- PlanWrite Permission = iota
- PrivateInvitesRead Permission = iota
- PrivateInvitesWrite Permission = iota
- ProfileRead Permission = iota
- ProfileWrite Permission = iota
- SigningKeysRead Permission = iota
- SigningKeysWrite Permission = iota
- StarringRead Permission = iota
- StarringWrite Permission = iota
- WatchingRead Permission = iota
- WatchingWrite Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- NoAccess: "no_access",
- ActionsRead: "actions:read",
- ActionsWrite: "actions:write",
- AdministrationRead: "administration:read",
- AdministrationWrite: "administration:write",
- CodeScanningAlertsRead: "code_scanning_alerts:read",
- CodeScanningAlertsWrite: "code_scanning_alerts:write",
- CodespacesRead: "codespaces:read",
- CodespacesWrite: "codespaces:write",
- CodespacesLifecycleRead: "codespaces_lifecycle:read",
- CodespacesLifecycleWrite: "codespaces_lifecycle:write",
- CodespacesMetadataRead: "codespaces_metadata:read",
- CodespacesMetadataWrite: "codespaces_metadata:write",
- CodespacesSecretsRead: "codespaces_secrets:read",
- CodespacesSecretsWrite: "codespaces_secrets:write",
- CommitStatusesRead: "commit_statuses:read",
- CommitStatusesWrite: "commit_statuses:write",
- ContentsRead: "contents:read",
- ContentsWrite: "contents:write",
- CustomPropertiesRead: "custom_properties:read",
- CustomPropertiesWrite: "custom_properties:write",
- DependabotAlertsRead: "dependabot_alerts:read",
- DependabotAlertsWrite: "dependabot_alerts:write",
- DependabotSecretsRead: "dependabot_secrets:read",
- DependabotSecretsWrite: "dependabot_secrets:write",
- DeploymentsRead: "deployments:read",
- DeploymentsWrite: "deployments:write",
- EnvironmentsRead: "environments:read",
- EnvironmentsWrite: "environments:write",
- IssuesRead: "issues:read",
- IssuesWrite: "issues:write",
- MergeQueuesRead: "merge_queues:read",
- MergeQueuesWrite: "merge_queues:write",
- MetadataRead: "metadata:read",
- MetadataWrite: "metadata:write",
- PagesRead: "pages:read",
- PagesWrite: "pages:write",
- PullRequestsRead: "pull_requests:read",
- PullRequestsWrite: "pull_requests:write",
- RepoSecurityRead: "repo_security:read",
- RepoSecurityWrite: "repo_security:write",
- SecretScanningRead: "secret_scanning:read",
- SecretScanningWrite: "secret_scanning:write",
- SecretsRead: "secrets:read",
- SecretsWrite: "secrets:write",
- VariablesRead: "variables:read",
- VariablesWrite: "variables:write",
- WebhooksRead: "webhooks:read",
- WebhooksWrite: "webhooks:write",
- WorkflowsRead: "workflows:read",
- WorkflowsWrite: "workflows:write",
- BlockUserRead: "block_user:read",
- BlockUserWrite: "block_user:write",
- CodespaceUserSecretsRead: "codespace_user_secrets:read",
- CodespaceUserSecretsWrite: "codespace_user_secrets:write",
- EmailRead: "email:read",
- EmailWrite: "email:write",
- FollowersRead: "followers:read",
- FollowersWrite: "followers:write",
- GpgKeysRead: "gpg_keys:read",
- GpgKeysWrite: "gpg_keys:write",
- GistsRead: "gists:read",
- GistsWrite: "gists:write",
- GitKeysRead: "git_keys:read",
- GitKeysWrite: "git_keys:write",
- LimitsRead: "limits:read",
- LimitsWrite: "limits:write",
- PlanRead: "plan:read",
- PlanWrite: "plan:write",
- PrivateInvitesRead: "private_invites:read",
- PrivateInvitesWrite: "private_invites:write",
- ProfileRead: "profile:read",
- ProfileWrite: "profile:write",
- SigningKeysRead: "signing_keys:read",
- SigningKeysWrite: "signing_keys:write",
- StarringRead: "starring:read",
- StarringWrite: "starring:write",
- WatchingRead: "watching:read",
- WatchingWrite: "watching:write",
- }
-
- StringToPermission = map[string]Permission{
- "no_access": NoAccess,
- "actions:read": ActionsRead,
- "actions:write": ActionsWrite,
- "administration:read": AdministrationRead,
- "administration:write": AdministrationWrite,
- "code_scanning_alerts:read": CodeScanningAlertsRead,
- "code_scanning_alerts:write": CodeScanningAlertsWrite,
- "codespaces:read": CodespacesRead,
- "codespaces:write": CodespacesWrite,
- "codespaces_lifecycle:read": CodespacesLifecycleRead,
- "codespaces_lifecycle:write": CodespacesLifecycleWrite,
- "codespaces_metadata:read": CodespacesMetadataRead,
- "codespaces_metadata:write": CodespacesMetadataWrite,
- "codespaces_secrets:read": CodespacesSecretsRead,
- "codespaces_secrets:write": CodespacesSecretsWrite,
- "commit_statuses:read": CommitStatusesRead,
- "commit_statuses:write": CommitStatusesWrite,
- "contents:read": ContentsRead,
- "contents:write": ContentsWrite,
- "custom_properties:read": CustomPropertiesRead,
- "custom_properties:write": CustomPropertiesWrite,
- "dependabot_alerts:read": DependabotAlertsRead,
- "dependabot_alerts:write": DependabotAlertsWrite,
- "dependabot_secrets:read": DependabotSecretsRead,
- "dependabot_secrets:write": DependabotSecretsWrite,
- "deployments:read": DeploymentsRead,
- "deployments:write": DeploymentsWrite,
- "environments:read": EnvironmentsRead,
- "environments:write": EnvironmentsWrite,
- "issues:read": IssuesRead,
- "issues:write": IssuesWrite,
- "merge_queues:read": MergeQueuesRead,
- "merge_queues:write": MergeQueuesWrite,
- "metadata:read": MetadataRead,
- "metadata:write": MetadataWrite,
- "pages:read": PagesRead,
- "pages:write": PagesWrite,
- "pull_requests:read": PullRequestsRead,
- "pull_requests:write": PullRequestsWrite,
- "repo_security:read": RepoSecurityRead,
- "repo_security:write": RepoSecurityWrite,
- "secret_scanning:read": SecretScanningRead,
- "secret_scanning:write": SecretScanningWrite,
- "secrets:read": SecretsRead,
- "secrets:write": SecretsWrite,
- "variables:read": VariablesRead,
- "variables:write": VariablesWrite,
- "webhooks:read": WebhooksRead,
- "webhooks:write": WebhooksWrite,
- "workflows:read": WorkflowsRead,
- "workflows:write": WorkflowsWrite,
- "block_user:read": BlockUserRead,
- "block_user:write": BlockUserWrite,
- "codespace_user_secrets:read": CodespaceUserSecretsRead,
- "codespace_user_secrets:write": CodespaceUserSecretsWrite,
- "email:read": EmailRead,
- "email:write": EmailWrite,
- "followers:read": FollowersRead,
- "followers:write": FollowersWrite,
- "gpg_keys:read": GpgKeysRead,
- "gpg_keys:write": GpgKeysWrite,
- "gists:read": GistsRead,
- "gists:write": GistsWrite,
- "git_keys:read": GitKeysRead,
- "git_keys:write": GitKeysWrite,
- "limits:read": LimitsRead,
- "limits:write": LimitsWrite,
- "plan:read": PlanRead,
- "plan:write": PlanWrite,
- "private_invites:read": PrivateInvitesRead,
- "private_invites:write": PrivateInvitesWrite,
- "profile:read": ProfileRead,
- "profile:write": ProfileWrite,
- "signing_keys:read": SigningKeysRead,
- "signing_keys:write": SigningKeysWrite,
- "starring:read": StarringRead,
- "starring:write": StarringWrite,
- "watching:read": WatchingRead,
- "watching:write": WatchingWrite,
- }
-
- PermissionIDs = map[Permission]int{
- NoAccess: 1,
- ActionsRead: 2,
- ActionsWrite: 3,
- AdministrationRead: 4,
- AdministrationWrite: 5,
- CodeScanningAlertsRead: 6,
- CodeScanningAlertsWrite: 7,
- CodespacesRead: 8,
- CodespacesWrite: 9,
- CodespacesLifecycleRead: 10,
- CodespacesLifecycleWrite: 11,
- CodespacesMetadataRead: 12,
- CodespacesMetadataWrite: 13,
- CodespacesSecretsRead: 14,
- CodespacesSecretsWrite: 15,
- CommitStatusesRead: 16,
- CommitStatusesWrite: 17,
- ContentsRead: 18,
- ContentsWrite: 19,
- CustomPropertiesRead: 20,
- CustomPropertiesWrite: 21,
- DependabotAlertsRead: 22,
- DependabotAlertsWrite: 23,
- DependabotSecretsRead: 24,
- DependabotSecretsWrite: 25,
- DeploymentsRead: 26,
- DeploymentsWrite: 27,
- EnvironmentsRead: 28,
- EnvironmentsWrite: 29,
- IssuesRead: 30,
- IssuesWrite: 31,
- MergeQueuesRead: 32,
- MergeQueuesWrite: 33,
- MetadataRead: 34,
- MetadataWrite: 35,
- PagesRead: 36,
- PagesWrite: 37,
- PullRequestsRead: 38,
- PullRequestsWrite: 39,
- RepoSecurityRead: 40,
- RepoSecurityWrite: 41,
- SecretScanningRead: 42,
- SecretScanningWrite: 43,
- SecretsRead: 44,
- SecretsWrite: 45,
- VariablesRead: 46,
- VariablesWrite: 47,
- WebhooksRead: 48,
- WebhooksWrite: 49,
- WorkflowsRead: 50,
- WorkflowsWrite: 51,
- BlockUserRead: 52,
- BlockUserWrite: 53,
- CodespaceUserSecretsRead: 54,
- CodespaceUserSecretsWrite: 55,
- EmailRead: 56,
- EmailWrite: 57,
- FollowersRead: 58,
- FollowersWrite: 59,
- GpgKeysRead: 60,
- GpgKeysWrite: 61,
- GistsRead: 62,
- GistsWrite: 63,
- GitKeysRead: 64,
- GitKeysWrite: 65,
- LimitsRead: 66,
- LimitsWrite: 67,
- PlanRead: 68,
- PlanWrite: 69,
- PrivateInvitesRead: 70,
- PrivateInvitesWrite: 71,
- ProfileRead: 72,
- ProfileWrite: 73,
- SigningKeysRead: 74,
- SigningKeysWrite: 75,
- StarringRead: 76,
- StarringWrite: 77,
- WatchingRead: 78,
- WatchingWrite: 79,
- }
-
- IdToPermission = map[int]Permission{
- 1: NoAccess,
- 2: ActionsRead,
- 3: ActionsWrite,
- 4: AdministrationRead,
- 5: AdministrationWrite,
- 6: CodeScanningAlertsRead,
- 7: CodeScanningAlertsWrite,
- 8: CodespacesRead,
- 9: CodespacesWrite,
- 10: CodespacesLifecycleRead,
- 11: CodespacesLifecycleWrite,
- 12: CodespacesMetadataRead,
- 13: CodespacesMetadataWrite,
- 14: CodespacesSecretsRead,
- 15: CodespacesSecretsWrite,
- 16: CommitStatusesRead,
- 17: CommitStatusesWrite,
- 18: ContentsRead,
- 19: ContentsWrite,
- 20: CustomPropertiesRead,
- 21: CustomPropertiesWrite,
- 22: DependabotAlertsRead,
- 23: DependabotAlertsWrite,
- 24: DependabotSecretsRead,
- 25: DependabotSecretsWrite,
- 26: DeploymentsRead,
- 27: DeploymentsWrite,
- 28: EnvironmentsRead,
- 29: EnvironmentsWrite,
- 30: IssuesRead,
- 31: IssuesWrite,
- 32: MergeQueuesRead,
- 33: MergeQueuesWrite,
- 34: MetadataRead,
- 35: MetadataWrite,
- 36: PagesRead,
- 37: PagesWrite,
- 38: PullRequestsRead,
- 39: PullRequestsWrite,
- 40: RepoSecurityRead,
- 41: RepoSecurityWrite,
- 42: SecretScanningRead,
- 43: SecretScanningWrite,
- 44: SecretsRead,
- 45: SecretsWrite,
- 46: VariablesRead,
- 47: VariablesWrite,
- 48: WebhooksRead,
- 49: WebhooksWrite,
- 50: WorkflowsRead,
- 51: WorkflowsWrite,
- 52: BlockUserRead,
- 53: BlockUserWrite,
- 54: CodespaceUserSecretsRead,
- 55: CodespaceUserSecretsWrite,
- 56: EmailRead,
- 57: EmailWrite,
- 58: FollowersRead,
- 59: FollowersWrite,
- 60: GpgKeysRead,
- 61: GpgKeysWrite,
- 62: GistsRead,
- 63: GistsWrite,
- 64: GitKeysRead,
- 65: GitKeysWrite,
- 66: LimitsRead,
- 67: LimitsWrite,
- 68: PlanRead,
- 69: PlanWrite,
- 70: PrivateInvitesRead,
- 71: PrivateInvitesWrite,
- 72: ProfileRead,
- 73: ProfileWrite,
- 74: SigningKeysRead,
- 75: SigningKeysWrite,
- 76: StarringRead,
- 77: StarringWrite,
- 78: WatchingRead,
- 79: WatchingWrite,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/github/finegrained/finegrained_test.go b/pkg/analyzer/analyzers/github/finegrained/finegrained_test.go
deleted file mode 100644
index 78cce954ee7c..000000000000
--- a/pkg/analyzer/analyzers/github/finegrained/finegrained_test.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package finegrained
-
-import (
- "testing"
- "time"
-
- gh "github.com/google/go-github/v67/github"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- analyzerCommon "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
-
- analyzerSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "analyzers1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- wantErr bool
- }{
- {
- name: "finegrained - github-allrepos-actionsRW-contentsRW-issuesRW",
- key: analyzerSecrets.MustGetField("GITHUB_FINEGRAINED_ALLREPOS_ACTIONS_RW_CONTENTS_RW_ISSUES_RW"),
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- cfg := &config.Config{}
- key := tt.key
- client := gh.NewClient(analyzers.NewAnalyzeClient(cfg)).WithAuthToken(key)
-
- md, err := analyzerCommon.GetTokenMetadata(key, client)
- if err != nil {
- t.Fatalf("could not get token metadata: %s", err)
- }
-
- _, err = AnalyzeFineGrainedToken(client, md, cfg.Shallow)
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/github/github.go b/pkg/analyzer/analyzers/github/github.go
deleted file mode 100644
index 46124bee7cf5..000000000000
--- a/pkg/analyzer/analyzers/github/github.go
+++ /dev/null
@@ -1,214 +0,0 @@
-package github
-
-import (
- "fmt"
- "strings"
- "time"
-
- "github.com/fatih/color"
- gh "github.com/google/go-github/v67/github"
- "golang.org/x/time/rate"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/classic"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/finegrained"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-// According to GitHub's rate limiting documentation, the default rate limit for
-// authenticated requests (PAT) is 5000 requests per hour. This equates to roughly 1.39
-// requests per second. To provide some buffer, we set the rate limit to 1.25
-// requests per second with a burst of 10.
-// https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28
-var rateLimiter = rate.NewLimiter(rate.Limit(1.25), 10)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeGitHub }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- info, err := AnalyzePermissions(a.Cfg, credInfo["key"])
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *common.SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := &analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeGitHub,
- Metadata: map[string]any{
- "owner": info.Metadata.User.Login,
- "type": info.Metadata.Type,
- "expiration": info.Metadata.Expiration,
- },
- }
- result.Bindings = append(result.Bindings, secretInfoToUserBindings(info)...)
- result.Bindings = append(result.Bindings, secretInfoToRepoBindings(info)...)
- result.Bindings = append(result.Bindings, secretInfoToGistBindings(info)...)
- for _, repo := range append(info.Repos, info.AccessibleRepos...) {
- if repo.Owner.GetType() != "Organization" {
- continue
- }
- name := repo.Owner.GetName()
- if name == "" {
- continue
- }
- result.UnboundedResources = append(result.UnboundedResources, analyzers.Resource{
- Name: name,
- FullyQualifiedName: fmt.Sprintf("github.com/%s", name),
- Type: "organization",
- })
- }
- // TODO: Unbound resources
- // - Repo owners
- // - Gist owners
- return result
-}
-
-func secretInfoToUserBindings(info *common.SecretInfo) []analyzers.Binding {
- return analyzers.BindAllPermissions(*userToResource(info.Metadata.User), info.Metadata.OauthScopes...)
-}
-
-func userToResource(user *gh.User) *analyzers.Resource {
- name := *user.Login
- return &analyzers.Resource{
- Name: name,
- FullyQualifiedName: fmt.Sprintf("github.com/%s", name),
- Type: strings.ToLower(*user.Type), // "user" or "organization"
- }
-}
-
-func secretInfoToRepoBindings(info *common.SecretInfo) []analyzers.Binding {
- var perms []analyzers.Permission
- switch info.Metadata.Type {
- case common.TokenTypeClassicPAT:
- perms = info.Metadata.OauthScopes
- case common.TokenTypeFineGrainedPAT:
- fineGrainedPermissions := info.RepoAccessMap.([]finegrained.Permission)
- for _, perm := range fineGrainedPermissions {
- permName, _ := perm.ToString()
- perms = append(perms, analyzers.Permission{Value: permName})
- }
- default:
- if len(info.Metadata.OauthScopes) > 0 {
- perms = info.Metadata.OauthScopes
- }
- }
-
- repos := info.Repos
- if len(info.AccessibleRepos) > 0 {
- repos = info.AccessibleRepos
- }
- var bindings []analyzers.Binding
- for _, repo := range repos {
- resource := analyzers.Resource{
- Name: *repo.Name,
- FullyQualifiedName: fmt.Sprintf("github.com/%s", *repo.FullName),
- Type: "repository",
- Parent: userToResource(repo.Owner),
- }
- bindings = append(bindings, analyzers.BindAllPermissions(resource, perms...)...)
- }
- return bindings
-}
-
-func secretInfoToGistBindings(info *common.SecretInfo) []analyzers.Binding {
- var bindings []analyzers.Binding
- for _, gist := range info.Gists {
- resource := analyzers.Resource{
- Name: *gist.Description,
- FullyQualifiedName: fmt.Sprintf("gist.github.com/%s/%s", *gist.Owner.Login, *gist.ID),
- Type: "gist",
- Parent: userToResource(gist.Owner),
- }
- bindings = append(bindings, analyzers.BindAllPermissions(resource, info.Metadata.OauthScopes...)...)
- }
- return bindings
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*common.SecretInfo, error) {
- if cfg == nil {
- cfg = &config.Config{}
- }
- client := gh.NewClient(analyzers.NewAnalyzeClient(cfg, analyzers.WithRateLimiter(rateLimiter))).WithAuthToken(key)
-
- md, err := common.GetTokenMetadata(key, client)
- if err != nil {
- return nil, err
- }
-
- if md.FineGrained {
- return finegrained.AnalyzeFineGrainedToken(client, md, cfg.Shallow)
- } else {
- return classic.AnalyzeClassicToken(client, md)
- }
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] %s", err.Error())
- return
- }
-
- color.Yellow("[i] Token User: %v", *info.Metadata.User.Login)
- if expiry := info.Metadata.Expiration; expiry.IsZero() {
- color.Red("[i] Token Expiration: does not expire")
- } else {
- timeRemaining := time.Until(expiry)
- color.Yellow("[i] Token Expiration: %v (%s remaining)", expiry, roughHumanReadableDuration(timeRemaining))
- }
- color.Yellow("[i] Token Type: %s\n\n", info.Metadata.Type)
-
- if info.Metadata.FineGrained {
- finegrained.PrintFineGrainedToken(cfg, info)
- return
- }
- classic.PrintClassicToken(cfg, info)
-}
-
-// roughHumanReadableDuration converts a duration into a rough estimate for
-// human consumption. The larger the duration, the larger granularity is
-// returned.
-func roughHumanReadableDuration(d time.Duration) string {
- var gran time.Duration
- var unit string
- switch {
- case d < 1*time.Minute:
- gran = time.Second
- unit = "second"
- case d < 1*time.Hour:
- gran = time.Minute
- unit = "minute"
- case d < 24*time.Hour:
- gran = time.Hour
- unit = "hour"
- case d < 4*7*24*time.Hour:
- gran = 24 * time.Hour
- unit = "day"
- case d < 3*4*7*24*time.Hour:
- gran = 7 * 24 * time.Hour
- unit = "week"
- case d < 5*365*24*time.Hour:
- gran = 365 * 24 * time.Hour
- unit = "month"
- default:
- gran = 365 * 24 * time.Hour
- unit = "year"
- }
- num := d.Round(gran) / gran
- if num != 1 {
- unit += "s"
- }
- return fmt.Sprintf("%d %s", num, unit)
-}
diff --git a/pkg/analyzer/analyzers/github/github_test.go b/pkg/analyzer/analyzers/github/github_test.go
deleted file mode 100644
index a3b9a9ee2e90..000000000000
--- a/pkg/analyzer/analyzers/github/github_test.go
+++ /dev/null
@@ -1,323 +0,0 @@
-package github
-
-import (
- "encoding/json"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- analyzerSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "analyzers1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "finegrained - github-allrepos-actionsRW-contentsRW-issuesRW",
- key: analyzerSecrets.MustGetField("GITHUB_FINEGRAINED_ALLREPOS_ACTIONS_RW_CONTENTS_RW_ISSUES_RW"),
- wantErr: false,
- want: `{
- "AnalyzerType": 7,
- "Bindings": [
- {
- "Resource": {
- "Name": "private",
- "FullyQualifiedName": "github.com/sirdetectsalot/private",
- "Type": "repository",
- "Metadata": null,
- "Parent": {
- "Name": "sirdetectsalot",
- "FullyQualifiedName": "github.com/sirdetectsalot",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "actions:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "private",
- "FullyQualifiedName": "github.com/sirdetectsalot/private",
- "Type": "repository",
- "Metadata": null,
- "Parent": {
- "Name": "sirdetectsalot",
- "FullyQualifiedName": "github.com/sirdetectsalot",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "contents:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "private",
- "FullyQualifiedName": "github.com/sirdetectsalot/private",
- "Type": "repository",
- "Metadata": null,
- "Parent": {
- "Name": "sirdetectsalot",
- "FullyQualifiedName": "github.com/sirdetectsalot",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "deployments:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "private",
- "FullyQualifiedName": "github.com/sirdetectsalot/private",
- "Type": "repository",
- "Metadata": null,
- "Parent": {
- "Name": "sirdetectsalot",
- "FullyQualifiedName": "github.com/sirdetectsalot",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "issues:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "private",
- "FullyQualifiedName": "github.com/sirdetectsalot/private",
- "Type": "repository",
- "Metadata": null,
- "Parent": {
- "Name": "sirdetectsalot",
- "FullyQualifiedName": "github.com/sirdetectsalot",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "metadata:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "public",
- "FullyQualifiedName": "github.com/sirdetectsalot/public",
- "Type": "repository",
- "Metadata": null,
- "Parent": {
- "Name": "sirdetectsalot",
- "FullyQualifiedName": "github.com/sirdetectsalot",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "actions:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "public",
- "FullyQualifiedName": "github.com/sirdetectsalot/public",
- "Type": "repository",
- "Metadata": null,
- "Parent": {
- "Name": "sirdetectsalot",
- "FullyQualifiedName": "github.com/sirdetectsalot",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "contents:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "public",
- "FullyQualifiedName": "github.com/sirdetectsalot/public",
- "Type": "repository",
- "Metadata": null,
- "Parent": {
- "Name": "sirdetectsalot",
- "FullyQualifiedName": "github.com/sirdetectsalot",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "deployments:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "public",
- "FullyQualifiedName": "github.com/sirdetectsalot/public",
- "Type": "repository",
- "Metadata": null,
- "Parent": {
- "Name": "sirdetectsalot",
- "FullyQualifiedName": "github.com/sirdetectsalot",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "issues:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "public",
- "FullyQualifiedName": "github.com/sirdetectsalot/public",
- "Type": "repository",
- "Metadata": null,
- "Parent": {
- "Name": "sirdetectsalot",
- "FullyQualifiedName": "github.com/sirdetectsalot",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "metadata:read",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {
- "owner": "sirdetectsalot",
- "expiration": "2026-03-24T15:27:38+05:00",
- "type": "Fine-Grained GitHub Personal Access Token"
- }
- }`,
- },
- {
- name: "v2 ghp",
- key: testSecrets.MustGetField("GITHUB_VERIFIED_GHP"),
- want: `{
- "AnalyzerType": 7,
- "Bindings": [
- {
- "Resource": {
- "Name": "truffle-sandbox",
- "FullyQualifiedName": "github.com/truffle-sandbox",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "notifications",
- "AccessLevel": "",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "public gist",
- "FullyQualifiedName": "gist.github.com/truffle-sandbox/fecf272c606ddbc5f8486f9c44821312",
- "Type": "gist",
- "Metadata": null,
- "Parent": {
- "Name": "truffle-sandbox",
- "FullyQualifiedName": "github.com/truffle-sandbox",
- "Type": "user",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "notifications",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {
- "owner": "truffle-sandbox",
- "expiration": "0001-01-01T00:00:00Z",
- "type": "Classic GitHub Personal Access Token"
- }
- }`,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON with indentation
- wantJSON, err := json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings and show diff if they don't match
- if string(gotJSON) != string(wantJSON) {
- diff := cmp.Diff(string(wantJSON), string(gotJSON))
- t.Errorf("Analyzer.Analyze() mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/gitlab/expected_output.json b/pkg/analyzer/analyzers/gitlab/expected_output.json
deleted file mode 100644
index fe0a8e7c8898..000000000000
--- a/pkg/analyzer/analyzers/gitlab/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":5,"Bindings":[{"Resource":{"Name":"gitlab.com/user/22466472","FullyQualifiedName":"gitlab.com/user/22466472","Type":"user","Metadata":{"token_created_at":"2024-08-15T06:33:00.337Z","token_expires_at":"2025-08-15","token_id":10470457,"token_name":"test-project-token","token_revoked":false},"Parent":null},"Permission":{"Value":"read_api","Parent":null}},{"Resource":{"Name":"gitlab.com/user/22466472","FullyQualifiedName":"gitlab.com/user/22466472","Type":"user","Metadata":{"token_created_at":"2024-08-15T06:33:00.337Z","token_expires_at":"2025-08-15","token_id":10470457,"token_name":"test-project-token","token_revoked":false},"Parent":null},"Permission":{"Value":"read_repository","Parent":null}},{"Resource":{"Name":"truffletester / trufflehog","FullyQualifiedName":"gitlab.com/project/60871295","Type":"project","Metadata":null,"Parent":null},"Permission":{"Value":"Developer","Parent":null}}],"UnboundedResources":null,"Metadata":{"enterprise":true}}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/gitlab/gitlab.go b/pkg/analyzer/analyzers/gitlab/gitlab.go
deleted file mode 100644
index 517774c030ac..000000000000
--- a/pkg/analyzer/analyzers/gitlab/gitlab.go
+++ /dev/null
@@ -1,371 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go gitlab
-
-package gitlab
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "os"
- "time"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-const (
- DefaultGitLabHost = "https://gitlab.com"
-)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeGitLab }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("key not found in credentialInfo")
- }
- host, ok := credInfo["host"]
- if !ok {
- host = DefaultGitLabHost
- }
-
- info, err := AnalyzePermissions(a.Cfg, key, host)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeGitLab,
- Metadata: map[string]any{
- "enterprise": info.Metadata.Enterprise,
- },
- Bindings: []analyzers.Binding{},
- }
-
- // Add user and it's permissions to bindings
- userFullyQualifiedName := fmt.Sprintf("gitlab.com/user/%d", info.AccessToken.UserID)
- userResource := analyzers.Resource{
- Name: userFullyQualifiedName,
- FullyQualifiedName: userFullyQualifiedName,
- Type: "user",
- Metadata: map[string]any{
- "token_name": info.AccessToken.Name,
- "token_id": info.AccessToken.ID,
- "token_created_at": info.AccessToken.CreatedAt,
- "token_revoked": info.AccessToken.Revoked,
- "token_expires_at": info.AccessToken.ExpiresAt,
- },
- }
-
- for _, scope := range info.AccessToken.Scopes {
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: userResource,
- Permission: analyzers.Permission{
- Value: scope,
- },
- })
- }
-
- // append project and it's permissions to bindings
- for _, project := range info.Projects {
- projectResource := analyzers.Resource{
- Name: project.NameWithNamespace,
- FullyQualifiedName: fmt.Sprintf("gitlab.com/project/%d", project.ID),
- Type: "project",
- }
-
- accessLevel, ok := access_level_map[project.Permissions.ProjectAccess.AccessLevel]
- if !ok {
- continue
- }
-
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: projectResource,
- Permission: analyzers.Permission{
- Value: accessLevel,
- },
- })
- }
-
- return &result
-}
-
-// consider calling /api/v4/metadata to learn about gitlab instance version and whether neterrprises is enabled
-
-// we'll call /api/v4/personal_access_tokens and then filter down to scopes.
-
-type AccessTokenJSON struct {
- ID int `json:"id"`
- Name string `json:"name"`
- Revoked bool `json:"revoked"`
- CreatedAt string `json:"created_at"`
- Scopes []string `json:"scopes"`
- LastUsedAt string `json:"last_used_at"`
- ExpiresAt string `json:"expires_at"`
- UserID int `json:"user_id"`
-}
-
-type ProjectsJSON struct {
- ID int `json:"id"`
- NameWithNamespace string `json:"name_with_namespace"`
- Permissions struct {
- ProjectAccess struct {
- AccessLevel int `json:"access_level"`
- } `json:"project_access"`
- } `json:"permissions"`
-}
-
-type ErrorJSON struct {
- Error string `json:"error"`
- Scope string `json:"scope"`
-}
-
-type MetadataJSON struct {
- Version string `json:"version"`
- Enterprise bool `json:"enterprise"`
-}
-
-func getPersonalAccessToken(cfg *config.Config, key, host string) (AccessTokenJSON, int, error) {
- var tokens AccessTokenJSON
-
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v4/personal_access_tokens/self", host), nil)
- if err != nil {
- return tokens, -1, err
- }
-
- req.Header.Set("Private-Token", key)
- resp, err := client.Do(req)
- if err != nil {
- return tokens, resp.StatusCode, err
- }
-
- defer resp.Body.Close()
- if err := json.NewDecoder(resp.Body).Decode(&tokens); err != nil {
- return tokens, resp.StatusCode, err
- }
- return tokens, resp.StatusCode, nil
-}
-
-func getAccessibleProjects(cfg *config.Config, key, host string) ([]ProjectsJSON, error) {
- var projects []ProjectsJSON
-
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v4/projects", host), nil)
- if err != nil {
- return projects, err
- }
-
- req.Header.Set("Private-Token", key)
-
- // Add query parameters
- q := req.URL.Query()
- q.Add("min_access_level", "10")
- req.URL.RawQuery = q.Encode()
-
- resp, err := client.Do(req)
- if err != nil {
- return projects, err
- }
-
- defer resp.Body.Close()
-
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return projects, err
- }
-
- newBody := func() io.ReadCloser {
- return io.NopCloser(bytes.NewReader(bodyBytes))
- }
-
- if err := json.NewDecoder(newBody()).Decode(&projects); err != nil {
- var e ErrorJSON
- if err := json.NewDecoder(newBody()).Decode(&e); err == nil {
- return projects, fmt.Errorf("Insufficient Scope to query for projects. We need api or read_api permissions.")
- }
- return projects, err
- }
- return projects, nil
-}
-
-func getMetadata(cfg *config.Config, key, host string) (MetadataJSON, error) {
- var metadata MetadataJSON
-
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v4/metadata", host), nil)
- if err != nil {
- return metadata, err
- }
-
- req.Header.Set("Private-Token", key)
- resp, err := client.Do(req)
- if err != nil {
- return metadata, err
- }
-
- defer resp.Body.Close()
-
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return metadata, err
- }
-
- newBody := func() io.ReadCloser {
- return io.NopCloser(bytes.NewReader(bodyBytes))
- }
-
- if err := json.NewDecoder(newBody()).Decode(&metadata); err != nil {
- return metadata, err
- }
-
- if metadata.Version == "" {
- var e ErrorJSON
- if err := json.NewDecoder(newBody()).Decode(&e); err != nil {
- return metadata, err
- }
- return metadata, fmt.Errorf("Insufficient Scope to query for metadata. We need read_user, ai_features, api or read_api permissions.")
- }
-
- return metadata, nil
-}
-
-type SecretInfo struct {
- AccessToken AccessTokenJSON
- Metadata MetadataJSON
- Projects []ProjectsJSON
-}
-
-func AnalyzePermissions(cfg *config.Config, key string, host string) (*SecretInfo, error) {
- // get personal_access_tokens accessible
- token, statusCode, err := getPersonalAccessToken(cfg, key, host)
- if err != nil {
- return nil, err
- }
- if statusCode != http.StatusOK {
- return nil, fmt.Errorf("Invalid GitLab Access Token")
- }
-
- meta, err := getMetadata(cfg, key, host)
- if err != nil {
- return nil, err
- }
-
- projects, err := getAccessibleProjects(cfg, key, host)
- if err != nil {
- return nil, err
- }
-
- return &SecretInfo{
- AccessToken: token,
- Metadata: meta,
- Projects: projects,
- }, nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key, DefaultGitLabHost)
- if err != nil {
- color.Red("[x] Error: %s", err)
- return
- }
-
- // print token info
- printTokenInfo(info.AccessToken)
-
- // print gitlab instance metadata
- if info.Metadata.Version != "" {
- printMetadata(info.Metadata)
- }
-
- // print token permissions
- printTokenPermissions(info.AccessToken)
-
- // print repos accessible
- if len(info.Projects) > 0 {
- printProjects(info.Projects)
- }
-}
-
-func getRemainingTime(t string) string {
- targetTime, err := time.Parse("2006-01-02", t)
- if err != nil {
- return ""
- }
-
- // Get the current time
- currentTime := time.Now()
-
- // Calculate the duration until the target time
- durationUntilTarget := targetTime.Sub(currentTime)
- durationUntilTarget = durationUntilTarget.Truncate(time.Minute)
-
- // Print the duration
- return fmt.Sprintf("%v", durationUntilTarget)
-}
-
-func printTokenInfo(token AccessTokenJSON) {
- color.Green("[!] Valid GitLab Access Token\n\n")
- color.Green("Token Name: %s\n", token.Name)
- color.Green("Created At: %s\n", token.CreatedAt)
- color.Green("Last Used At: %s\n", token.LastUsedAt)
- color.Green("User ID: %d\n", token.UserID)
- color.Green("Expires At: %s (%v remaining)\n\n", token.ExpiresAt, getRemainingTime(token.ExpiresAt))
- if token.Revoked {
- color.Red("Token Revoked: %v\n", token.Revoked)
- }
-}
-
-func printMetadata(metadata MetadataJSON) {
- color.Green("[i] GitLab Instance Metadata\n")
- color.Green("Version: %s\n", metadata.Version)
- color.Green("Enterprise: %v\n\n", metadata.Enterprise)
-}
-
-func printTokenPermissions(token AccessTokenJSON) {
- color.Green("[i] Token Permissions\n")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Scope", "Access" /* Add more column headers if needed */})
- for _, scope := range token.Scopes {
- t.AppendRow([]any{color.GreenString(scope), color.GreenString(gitlab_scopes[scope])})
- }
- t.SetColumnConfigs([]table.ColumnConfig{
- {Number: 2, WidthMax: 100}, // Limit the width of the third column (Description) to 20 characters
- })
- t.Render()
-}
-
-func printProjects(projects []ProjectsJSON) {
- color.Green("\n[i] Accessible Projects\n")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Project", "Access Level" /* Add more column headers if needed */})
- for _, project := range projects {
- access := access_level_map[project.Permissions.ProjectAccess.AccessLevel]
- if project.Permissions.ProjectAccess.AccessLevel == 50 {
- access = color.GreenString(access)
- } else if project.Permissions.ProjectAccess.AccessLevel >= 30 {
- access = color.YellowString(access)
- } else {
- access = color.RedString(access)
- }
- t.AppendRow([]any{color.GreenString(project.NameWithNamespace), access})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/gitlab/gitlab_test.go b/pkg/analyzer/analyzers/gitlab/gitlab_test.go
deleted file mode 100644
index bc62df9b36b1..000000000000
--- a/pkg/analyzer/analyzers/gitlab/gitlab_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package gitlab
-
-import (
- _ "embed"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid gitlab access token",
- key: testSecrets.MustGetField("GITLABV2"),
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = \n%s", gotIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/gitlab/permissions.go b/pkg/analyzer/analyzers/gitlab/permissions.go
deleted file mode 100644
index 359ea85bc824..000000000000
--- a/pkg/analyzer/analyzers/gitlab/permissions.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package gitlab
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Api Permission = iota
- ReadUser Permission = iota
- ReadApi Permission = iota
- ReadRepository Permission = iota
- WriteRepository Permission = iota
- ReadRegistry Permission = iota
- WriteRegistry Permission = iota
- Sudo Permission = iota
- AdminMode Permission = iota
- CreateRunner Permission = iota
- ManageRunner Permission = iota
- AiFeatures Permission = iota
- K8sProxy Permission = iota
- ReadServicePing Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Api: "api",
- ReadUser: "read_user",
- ReadApi: "read_api",
- ReadRepository: "read_repository",
- WriteRepository: "write_repository",
- ReadRegistry: "read_registry",
- WriteRegistry: "write_registry",
- Sudo: "sudo",
- AdminMode: "admin_mode",
- CreateRunner: "create_runner",
- ManageRunner: "manage_runner",
- AiFeatures: "ai_features",
- K8sProxy: "k8s_proxy",
- ReadServicePing: "read_service_ping",
- }
-
- StringToPermission = map[string]Permission{
- "api": Api,
- "read_user": ReadUser,
- "read_api": ReadApi,
- "read_repository": ReadRepository,
- "write_repository": WriteRepository,
- "read_registry": ReadRegistry,
- "write_registry": WriteRegistry,
- "sudo": Sudo,
- "admin_mode": AdminMode,
- "create_runner": CreateRunner,
- "manage_runner": ManageRunner,
- "ai_features": AiFeatures,
- "k8s_proxy": K8sProxy,
- "read_service_ping": ReadServicePing,
- }
-
- PermissionIDs = map[Permission]int{
- Api: 1,
- ReadUser: 2,
- ReadApi: 3,
- ReadRepository: 4,
- WriteRepository: 5,
- ReadRegistry: 6,
- WriteRegistry: 7,
- Sudo: 8,
- AdminMode: 9,
- CreateRunner: 10,
- ManageRunner: 11,
- AiFeatures: 12,
- K8sProxy: 13,
- ReadServicePing: 14,
- }
-
- IdToPermission = map[int]Permission{
- 1: Api,
- 2: ReadUser,
- 3: ReadApi,
- 4: ReadRepository,
- 5: WriteRepository,
- 6: ReadRegistry,
- 7: WriteRegistry,
- 8: Sudo,
- 9: AdminMode,
- 10: CreateRunner,
- 11: ManageRunner,
- 12: AiFeatures,
- 13: K8sProxy,
- 14: ReadServicePing,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/gitlab/permissions.yaml b/pkg/analyzer/analyzers/gitlab/permissions.yaml
deleted file mode 100644
index 2dfb858ebd3d..000000000000
--- a/pkg/analyzer/analyzers/gitlab/permissions.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-permissions:
- - api
- - read_user
- - read_api
- - read_repository
- - write_repository
- - read_registry
- - write_registry
- - sudo
- - admin_mode
- - create_runner
- - manage_runner
- - ai_features
- - k8s_proxy
- - read_service_ping
diff --git a/pkg/analyzer/analyzers/gitlab/scopes.go b/pkg/analyzer/analyzers/gitlab/scopes.go
deleted file mode 100644
index ad8a36ce513e..000000000000
--- a/pkg/analyzer/analyzers/gitlab/scopes.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package gitlab
-
-var gitlab_scopes = map[string]string{
- "api": "Grants complete read/write access to the API, including all groups and projects, the container registry, the dependency proxy, and the package registry. Also grants complete read/write access to the registry and repository using Git over HTTP.",
- "read_user": "Grants read-only access to the authenticated user’s profile through the /user API endpoint, which includes username, public email, and full name. Also grants access to read-only API endpoints under /users.",
- "read_api": "Grants read access to the API, including all groups and projects, the container registry, and the package registry.",
- "read_repository": "Grants read-only access to repositories on private projects using Git-over-HTTP or the Repository Files API.",
- "write_repository": "Grants read-write access to repositories on private projects using Git-over-HTTP (not using the API).",
- "read_registry": "Grants read-only (pull) access to container registry images if a project is private and authorization is required. Available only when the container registry is enabled.",
- "write_registry": "Grants read-write (push) access to container registry images if a project is private and authorization is required. Available only when the container registry is enabled.",
- "sudo": "Grants permission to perform API actions as any user in the system, when authenticated as an administrator.",
- "admin_mode": "Grants permission to perform API actions as an administrator, when Admin Mode is enabled. (Introduced in GitLab 15.8.)",
- "create_runner": "Grants permission to create runners.",
- "manage_runner": "Grants permission to manage runners.",
- "ai_features": "Grants permission to perform API actions for GitLab Duo. This scope is designed to work with the GitLab Duo Plugin for JetBrains. For all other extensions, see scope requirements.",
- "k8s_proxy": "Grants permission to perform Kubernetes API calls using the agent for Kubernetes.",
- "read_service_ping": "Grant access to download Service Ping payload through the API when authenticated as an admin use. (Introduced in GitLab 16.8.",
-}
-
-var access_level_map = map[int]string{
- 0: "No access",
- 5: "Minimal access",
- 10: "Guest",
- 20: "Reporter",
- 30: "Developer",
- 40: "Maintainer",
- 50: "Owner",
-}
diff --git a/pkg/analyzer/analyzers/groq/groq.go b/pkg/analyzer/analyzers/groq/groq.go
deleted file mode 100644
index 66f016aa721b..000000000000
--- a/pkg/analyzer/analyzers/groq/groq.go
+++ /dev/null
@@ -1,158 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go groq
-package groq
-
-import (
- "errors"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-// SecretInfo hold the information about the groq key
-type SecretInfo struct {
- Valid bool
- Reference string
- GroqResources []GroqResource
- Permissions []string
- Misc map[string]string
-}
-
-// GroqResource is a single groq resource which can be accessed with groq api key
-type GroqResource struct {
- ID string
- Name string
- Type string
- Permission string
- Metadata map[string]string
-}
-
-// appendGroqResource append the single groq resource to secretinfo groqresources list
-func (s *SecretInfo) appendGroqResource(resource GroqResource) {
- s.GroqResources = append(s.GroqResources, resource)
-}
-
-// updateMetadata safely update the metadata of the groq resource
-func (g GroqResource) updateMetadata(key, value string) {
- if g.Metadata == nil {
- g.Metadata = map[string]string{}
- }
-
- g.Metadata[key] = value
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeGroq
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, exist := credInfo["key"]
- if !exist {
- return nil, errors.New("key not found in credentials info")
- }
-
- secretInfo, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(secretInfo), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- // just print the error in cli and continue as a partial success
- color.Red("[x] Invalid Groq API key\n")
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- color.Green("[i] Valid Groq API key\n")
- color.Yellow("\n[i] Permission: Full Access\n")
-
- if len(info.GroqResources) > 0 {
- printGroqResources(info.GroqResources)
- }
-
- color.Yellow("\n[!] Expires: Never")
-}
-
-func AnalyzePermissions(cfg *config.Config, apiKey string) (*SecretInfo, error) {
- // create a HTTP client
- client := analyzers.NewAnalyzeClient(cfg)
-
- var secretInfo = &SecretInfo{Valid: true}
-
- if err := captureBatches(client, apiKey, secretInfo); err != nil {
- return nil, err
- }
-
- if err := captureFiles(client, apiKey, secretInfo); err != nil {
- return nil, err
- }
-
- return secretInfo, nil
-}
-
-// secretInfoToAnalyzerResult translate secret info to Analyzer Result
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeGroq,
- Metadata: map[string]any{"Valid_Key": info.Valid},
- Bindings: make([]analyzers.Binding, len(info.GroqResources)),
- }
-
- // extract information to create bindings and append to result bindings
- for _, groqResource := range info.GroqResources {
- binding := analyzers.Binding{
- Resource: analyzers.Resource{
- Name: groqResource.Name,
- FullyQualifiedName: groqResource.ID,
- Type: groqResource.Type,
- Metadata: map[string]any{},
- },
- Permission: analyzers.Permission{
- Value: groqResource.Permission,
- },
- }
-
- for key, value := range groqResource.Metadata {
- binding.Resource.Metadata[key] = value
- }
-
- result.Bindings = append(result.Bindings, binding)
- }
-
- return &result
-}
-
-func printGroqResources(resources []GroqResource) {
- color.Green("\n[i] Resources:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Name", "Type"})
- for _, resource := range resources {
- t.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/groq/groq_test.go b/pkg/analyzer/analyzers/groq/groq_test.go
deleted file mode 100644
index ef73d217b933..000000000000
--- a/pkg/analyzer/analyzers/groq/groq_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package groq
-
-import (
- _ "embed"
- "encoding/json"
- "fmt"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- apiKey := testSecrets.MustGetField("GROQ")
-
- tests := []struct {
- name string
- apiKey string
- want string
- wantErr bool
- }{
- {
- name: "valid dockerhub credentials",
- apiKey: apiKey,
- want: `{"AnalyzerType":2,"Bindings":[],"UnboundedResources":null,"Metadata":{"Valid_Key":true}}`,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.apiKey})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- fmt.Println(string(gotJSON))
-
- // compare the JSON strings
- if string(gotJSON) != string(tt.want) {
- // pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(tt.want, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/groq/permissions.go b/pkg/analyzer/analyzers/groq/permissions.go
deleted file mode 100644
index 9d3a6c4c1480..000000000000
--- a/pkg/analyzer/analyzers/groq/permissions.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package groq
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- FullAccess Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- FullAccess: "full_access",
- }
-
- StringToPermission = map[string]Permission{
- "full_access": FullAccess,
- }
-
- PermissionIDs = map[Permission]int{
- FullAccess: 1,
- }
-
- IdToPermission = map[int]Permission{
- 1: FullAccess,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/groq/permissions.yaml b/pkg/analyzer/analyzers/groq/permissions.yaml
deleted file mode 100644
index c20c81c9234b..000000000000
--- a/pkg/analyzer/analyzers/groq/permissions.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-permissions:
- - full_access # by default groq api key has full access
diff --git a/pkg/analyzer/analyzers/groq/requests.go b/pkg/analyzer/analyzers/groq/requests.go
deleted file mode 100644
index b8795058d0c9..000000000000
--- a/pkg/analyzer/analyzers/groq/requests.go
+++ /dev/null
@@ -1,187 +0,0 @@
-package groq
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "time"
-)
-
-var (
- permissionErr = "permissions_error"
- notAvailableForPlan = "not_available_for_plan"
-)
-
-// errorResponse is the response from groq APIs in case of any error
-type errorResponse struct {
- Error struct {
- Message string `json:"message"`
- Type string `json:"type"`
- Code string `json:"code"`
- } `json:"error"`
-}
-
-// listBatchesResponse is the response of /v1/batches API
-type listBatchesResponse struct {
- Data []batch `json:"data"`
-}
-
-// batch represent a single batch inside batches list
-type batch struct {
- ID string `json:"id"`
- Object string `json:"object"`
- Endpoint string `json:"endpoint"`
- InputFileID string `json:"input_file_id"`
- Status string `json:"status"`
- ExpiresAt int64 `json:"expires_at"`
-}
-
-// listBatchesResponse is the response of /v1/files API
-type listFilesResponse struct {
- Data []file `json:"data"`
-}
-
-// file represents a single file object inside files list
-type file struct {
- ID string `json:"id"`
- Object string `json:"object"`
- CreatedAt int64 `json:"created_at"`
- Filename string `json:"filename"`
- Purpose string `json:"purpose"`
-}
-
-func isPermissionError(err errorResponse) bool {
- // has permissions error or not available for the plan subscribed
- if err.Error.Type == permissionErr && err.Error.Code == notAvailableForPlan {
- return true
- }
-
- return false
-}
-
-// makeGroqRequest send the API request to passed url with passed key as API Key and return response body and status code
-func makeGroqRequest(client *http.Client, url, key string) ([]byte, int, error) {
- // create request
- req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
- if err != nil {
- return nil, 0, err
- }
-
- // add required keys in the header
- req.Header.Set("Authorization", "Bearer "+key)
- req.Header.Set("Content-Type", "application/json")
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, 0, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- responseBodyByte, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, 0, err
- }
-
- return responseBodyByte, resp.StatusCode, nil
-}
-
-// docs: https://console.groq.com/docs/api-reference#batches-list
-func captureBatches(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeGroqRequest(client, "https://api.groq.com/openai/v1/batches", key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var batches listBatchesResponse
-
- if err := json.Unmarshal(response, &batches); err != nil {
- return err
- }
-
- for _, batch := range batches.Data {
- resource := GroqResource{
- ID: batch.ID,
- Name: batch.ID, // no specific name for batch
- Type: batch.Object,
- Permission: PermissionStrings[FullAccess],
- }
-
- resource.updateMetadata("status", batch.Status)
- resource.updateMetadata("endpoint", batch.Endpoint)
- resource.updateMetadata("input file id", batch.InputFileID)
- resource.updateMetadata("expires at", time.Unix(batch.ExpiresAt, 0).UTC().Format("2006-01-02 15:04:05 UTC"))
-
- secretInfo.appendGroqResource(resource)
- }
-
- return nil
- case http.StatusForbidden:
- var errResp errorResponse
-
- if err := json.Unmarshal(response, &errResp); err != nil {
- return err
- }
-
- if isPermissionError(errResp) {
- return nil
- }
-
- return fmt.Errorf("unexpected error: %s", errResp.Error.Message)
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://console.groq.com/docs/api-reference#files-list
-func captureFiles(client *http.Client, key string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeGroqRequest(client, "https://api.groq.com/openai/v1/files", key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var files listFilesResponse
-
- if err := json.Unmarshal(response, &files); err != nil {
- return err
- }
-
- for _, file := range files.Data {
- resource := GroqResource{
- ID: file.ID,
- Name: file.Filename,
- Type: file.Object,
- Permission: PermissionStrings[FullAccess],
- }
-
- resource.updateMetadata("purpose", file.Purpose)
- resource.updateMetadata("created at", time.Unix(file.CreatedAt, 0).UTC().Format("2006-01-02 15:04:05 UTC"))
-
- secretInfo.appendGroqResource(resource)
- }
-
- return nil
- case http.StatusForbidden:
- var errResp errorResponse
-
- if err := json.Unmarshal(response, &errResp); err != nil {
- return err
- }
-
- if isPermissionError(errResp) {
- return nil
- }
-
- return fmt.Errorf("unexpected error: %s", errResp.Error.Message)
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
diff --git a/pkg/analyzer/analyzers/huggingface/huggingface.go b/pkg/analyzer/analyzers/huggingface/huggingface.go
deleted file mode 100644
index b4508b56eb7d..000000000000
--- a/pkg/analyzer/analyzers/huggingface/huggingface.go
+++ /dev/null
@@ -1,667 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go huggingface
-
-package huggingface
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "os"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-const (
- FINEGRAINED = "fineGrained"
- WRITE = "write"
- READ = "read"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeHuggingFace }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok || key == "" {
- return nil, fmt.Errorf("key not found in credentialInfo")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func bakeUnboundedResources(tokenJSON HFTokenJSON) []analyzers.Resource {
- unboundedResources := make([]analyzers.Resource, len(tokenJSON.Orgs))
- for idx, org := range tokenJSON.Orgs {
- unboundedResources[idx] = analyzers.Resource{
- Name: org.Name,
- FullyQualifiedName: "huggingface.com/user/" + tokenJSON.Username + "/organization/" + org.Name,
- Type: "organization",
- Metadata: map[string]interface{}{
- "role": org.Role,
- "is_enterprise": org.IsEnterprise,
- },
- }
- }
- return unboundedResources
-}
-
-func bakeUnfineGrainedBindings(allModels []Model, tokenJSON HFTokenJSON) []analyzers.Binding {
- bindings := make([]analyzers.Binding, len(allModels))
-
- for idx, model := range allModels {
- // Add Read Privs to All Models
- modelResource := analyzers.Resource{
- Name: model.Name,
- FullyQualifiedName: "huggingface.com/model/" + model.ID,
- Type: "model",
- Metadata: map[string]interface{}{
- "private": model.Private,
- },
- }
-
- // means both read & write permission for the model
- accessLevel := string(analyzers.READ)
- if tokenJSON.Auth.AccessToken.Type == WRITE {
- accessLevel = string(analyzers.WRITE)
- }
- bindings[idx] = analyzers.Binding{
- Resource: modelResource,
- Permission: analyzers.Permission{
- Value: string(accessLevel),
- },
- }
- }
- return bindings
-}
-
-// finegrained scopes are grouped by org, user or model.
-func bakefineGrainedBindings(allModels []Model, tokenJSON HFTokenJSON) []analyzers.Binding {
- // this section will extract the relevant permissions for each entity and store them in a map
- var nameToPermissions = make(map[string]analyzers.Permission)
- for _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {
- privs := analyzers.Permission{
- Value: string(analyzers.NONE),
- }
- for _, perm := range permission.Permissions {
- if perm == "repo.content.read" {
- privs.Value = string(analyzers.READ)
- } else if perm == "repo.write" {
- privs.Value = string(analyzers.WRITE)
- }
- }
- if permission.Entity.Type == "user" || permission.Entity.Type == "org" {
- nameToPermissions[permission.Entity.Name] = privs
- } else if permission.Entity.Type == "model" {
- nameToPermissions[modelNameLookup(allModels, permission.Entity.ID)] = privs
- }
- }
-
- bindings := make([]analyzers.Binding, len(allModels))
- for idx, model := range allModels {
-
- // Add Read Privs to All Models
- modelResource := analyzers.Resource{
- Name: model.Name,
- FullyQualifiedName: "huggingface.com/model/" + model.ID,
- Type: "model",
- Metadata: map[string]interface{}{
- "private": model.Private,
- },
- }
-
- var perm analyzers.Permission
- // get username/orgname for each model and apply those permissions
- modelUsername := strings.Split(model.Name, "/")[0]
- if permissions, ok := nameToPermissions[modelUsername]; ok {
- perm = permissions
- }
- // override model permissions with repo-specific permissions
- if permissions, ok := nameToPermissions[model.Name]; ok {
- perm = permissions
- }
-
- bindings[idx] = analyzers.Binding{
- Resource: modelResource,
- Permission: perm,
- }
- }
- return bindings
-}
-
-func bakeOrganizationBindings(tokenJSON HFTokenJSON) []analyzers.Binding {
- // check if there are any org permissions
- // if so, save them as a map. Only need to do this once
- // even if multiple orgs b/c as of 6/6/24, users can only define one set of scopes
- // for all orgs referenced on an access token
- orgPermissions := map[string]struct{}{}
- var orgResource *analyzers.Resource = nil
- for _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {
- if permission.Entity.Type == "org" {
- orgResource = &analyzers.Resource{
- Name: permission.Entity.Name,
- FullyQualifiedName: "hugggingface.com/organization/" + permission.Entity.ID,
- Type: "organization",
- }
- for _, perm := range permission.Permissions {
- orgPermissions[perm] = struct{}{}
- }
- break
- }
- }
-
- bindings := make([]analyzers.Binding, 0)
- // check if there are any org permissions
- if orgResource == nil {
- return bindings
- }
-
- for _, permission := range org_scopes_order {
- for key, value := range org_scopes[permission] {
- if _, ok := orgPermissions[key]; ok {
- bindings = append(bindings, analyzers.Binding{
- Resource: *orgResource,
- Permission: analyzers.Permission{
- Value: value,
- },
- })
- }
- }
- }
-
- return bindings
-}
-
-func bakeUserBindings(tokenJSON HFTokenJSON) []analyzers.Binding {
- bindings := make([]analyzers.Binding, 0)
- // build a map of all user permissions
- users := map[string]struct{}{}
- userPermissions := map[string]struct{}{}
- for _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {
- if permission.Entity.Type == "user" {
- users[permission.Entity.Name] = struct{}{}
- for _, perm := range permission.Permissions {
- userPermissions[perm] = struct{}{}
- }
- }
- }
-
- // global permissions only apply to user tokens as of 6/6/24
- // but there would be a naming collision in the scopes document
- // so we prepend "global." to the key and then add to the map
- for _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Global {
- userPermissions["global."+permission] = struct{}{}
- }
-
- // check if there are any user permissions
- if len(userPermissions) == 0 {
- return bindings
- }
-
- userResource := analyzers.Resource{
- Name: tokenJSON.Name,
- FullyQualifiedName: "huggingface.com/user/" + tokenJSON.Username,
- Type: "user",
- }
- for _, permission := range user_scopes_order {
- for key, value := range user_scopes[permission] {
- if _, ok := userPermissions[key]; ok {
- bindings = append(bindings, analyzers.Binding{
- Resource: userResource,
- Permission: analyzers.Permission{
- Value: value,
- },
- })
- }
- }
- }
-
- return bindings
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeHuggingFace,
- Metadata: map[string]interface{}{
- "username": info.Token.Username,
- "name": info.Token.Name,
- "token_name": info.Token.Auth.AccessToken.Name,
- "token_type": info.Token.Auth.AccessToken.Type,
- },
- }
-
- if len(info.Token.Orgs) > 0 {
- result.UnboundedResources = bakeUnboundedResources(info.Token)
- }
-
- result.Bindings = make([]analyzers.Binding, 0)
-
- if info.Token.Auth.AccessToken.Type == FINEGRAINED {
- result.Bindings = append(result.Bindings, bakefineGrainedBindings(info.Models, info.Token)...)
- result.Bindings = append(result.Bindings, bakeOrganizationBindings(info.Token)...)
- result.Bindings = append(result.Bindings, bakeUserBindings(info.Token)...)
- } else {
- result.Bindings = append(result.Bindings, bakeUnfineGrainedBindings(info.Models, info.Token)...)
- }
-
- return &result
-}
-
-// HFTokenJSON is the struct for the HF /whoami-v2 API JSON response
-type HFTokenJSON struct {
- Username string `json:"name"`
- Name string `json:"fullname"`
- Orgs []struct {
- Name string `json:"name"`
- Role string `json:"roleInOrg"`
- IsEnterprise bool `json:"isEnterprise"`
- } `json:"orgs"`
- Auth struct {
- AccessToken struct {
- Name string `json:"displayName"`
- Type string `json:"role"`
- CreatedAt string `json:"createdAt"`
- FineGrained struct {
- Global []string `json:"global"`
- Scoped []struct {
- Entity struct {
- Type string `json:"type"`
- Name string `json:"name"`
- ID string `json:"_id"`
- } `json:"entity"`
- Permissions []string `json:"permissions"`
- } `json:"scoped"`
- } `json:"fineGrained"`
- }
- } `json:"auth"`
-}
-
-type Permissions struct {
- Read bool
- Write bool
-}
-
-type Model struct {
- Name string `json:"id"`
- ID string `json:"_id"`
- Private bool `json:"private"`
- Permissions Permissions
-}
-
-// getModelsByAuthor calls the HF API /models endpoint with the author query param
-// returns a list of models and an error
-func getModelsByAuthor(cfg *config.Config, key string, author string) ([]Model, error) {
- var modelsJSON []Model
-
- // create a new request
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", "https://huggingface.co/api/models", nil)
- if err != nil {
- return modelsJSON, err
- }
-
- // Add bearer token
- req.Header.Add("Authorization", "Bearer "+key)
-
- // Add author param
- q := req.URL.Query()
- q.Add("author", author)
- req.URL.RawQuery = q.Encode()
-
- // send the request
- resp, err := client.Do(req)
- if err != nil {
- return modelsJSON, err
- }
-
- // defer the response body closing
- defer resp.Body.Close()
-
- // read response
- if err := json.NewDecoder(resp.Body).Decode(&modelsJSON); err != nil {
- return modelsJSON, err
- }
- return modelsJSON, nil
-}
-
-// getTokenInfo calls the HF API /whoami-v2 endpoint to get the token info
-// returns the token info, a boolean indicating token validity, and an error
-func getTokenInfo(cfg *config.Config, key string) (HFTokenJSON, bool, error) {
- var tokenJSON HFTokenJSON
-
- // create a new request
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", "https://huggingface.co/api/whoami-v2", nil)
- if err != nil {
- return tokenJSON, false, err
- }
-
- // Add bearer token
- req.Header.Add("Authorization", "Bearer "+key)
-
- // send the request
- resp, err := client.Do(req)
- if err != nil {
- return tokenJSON, false, err
- }
-
- // check if the response is 200
- if resp.StatusCode != 200 {
- return tokenJSON, false, nil
- }
-
- // defer the response body closing
- defer resp.Body.Close()
-
- // read response
- if err := json.NewDecoder(resp.Body).Decode(&tokenJSON); err != nil {
- return tokenJSON, true, err
- }
- return tokenJSON, true, nil
-}
-
-type SecretInfo struct {
- Token HFTokenJSON
- Models []Model
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- // get token info
- token, success, err := getTokenInfo(cfg, key)
- if err != nil {
- return nil, err
- }
-
- if !success {
- return nil, fmt.Errorf("Invalid HuggingFace Access Token")
- }
-
- // get all models by username
- var allModels []Model
- userModels, err := getModelsByAuthor(cfg, key, token.Username)
- if err != nil {
- return nil, err
- }
- allModels = append(allModels, userModels...)
-
- // get all models from all orgs
- for _, org := range token.Orgs {
- orgModels, err := getModelsByAuthor(cfg, key, org.Name)
- if err != nil {
- return nil, err
- }
- allModels = append(allModels, orgModels...)
- }
-
- return &SecretInfo{
- Token: token,
- Models: allModels,
- }, nil
-}
-
-// AnalyzeAndPrintPermissions prints the permissions of a HuggingFace API key
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
-
- color.Green("[!] Valid HuggingFace Access Token\n\n")
-
- // print user info
- color.Yellow("[i] Username: " + info.Token.Username)
- color.Yellow("[i] Name: " + info.Token.Name)
- color.Yellow("[i] Token Name: " + info.Token.Auth.AccessToken.Name)
- color.Yellow("[i] Token Type: " + info.Token.Auth.AccessToken.Type)
-
- // print org info
- printOrgs(info.Token)
-
- // print accessible models
- printAccessibleModels(info.Models, info.Token)
-
- if info.Token.Auth.AccessToken.Type == FINEGRAINED {
- // print org permissions
- printOrgPermissions(info.Token)
-
- // print user permissions
- printUserPermissions(info.Token)
- }
-}
-
-// printUserPermissions prints the user permissions
-// only applies to fine-grained tokens
-func printUserPermissions(tokenJSON HFTokenJSON) {
- color.Green("\n[i] User Permissions:")
-
- // build a map of all user permissions
- userPermissions := map[string]struct{}{}
- for _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {
- if permission.Entity.Type == "user" {
- for _, perm := range permission.Permissions {
- userPermissions[perm] = struct{}{}
- }
- }
- }
-
- // global permissions only apply to user tokens as of 6/6/24
- // but there would be a naming collision in the scopes document
- // so we prepend "global." to the key and then add to the map
- for _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Global {
- userPermissions["global."+permission] = struct{}{}
- }
-
- // check if there are any user permissions
- if len(userPermissions) == 0 {
- color.Red("\tNo user permissions scoped.")
- return
- }
-
- // print the user permissions
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Category", "Permission", "In-Scope"})
-
- for _, permission := range user_scopes_order {
- t.AppendRow([]interface{}{permission, "---", "---"})
- for key, value := range user_scopes[permission] {
- if _, ok := userPermissions[key]; ok {
- t.AppendRow([]interface{}{"", color.GreenString(value), color.GreenString("True")})
- } else {
- t.AppendRow([]interface{}{"", value, "False"})
- }
- }
- }
- t.Render()
-}
-
-// printOrgPermissions prints the organization permissions
-// only applies to fine-grained tokens
-func printOrgPermissions(tokenJSON HFTokenJSON) {
- color.Green("\n[i] Organization Permissions:")
-
- // check if there are any org permissions
- // if so, save them as a map. Only need to do this once
- // even if multiple orgs b/c as of 6/6/24, users can only define one set of scopes
- // for all orgs referenced on an access token
- orgScoped := false
- orgPermissions := map[string]struct{}{}
- for _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {
- if permission.Entity.Type == "org" {
- orgScoped = true
- for _, perm := range permission.Permissions {
- orgPermissions[perm] = struct{}{}
- }
- break
- }
- }
-
- // check if there are any org permissions
- if !orgScoped {
- color.Red("\tNo organization permissions scoped.")
- return
- }
-
- // print the org permissions
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Category", "Permission", "In-Scope"})
-
- for _, permission := range org_scopes_order {
- t.AppendRow([]interface{}{permission, "---", "---"})
- for key, value := range org_scopes[permission] {
- if _, ok := orgPermissions[key]; ok {
- t.AppendRow([]interface{}{"", color.GreenString(value), color.GreenString("True")})
- } else {
- t.AppendRow([]interface{}{"", value, "False"})
- }
- }
- }
- t.Render()
-}
-
-// printOrgs prints the organizations the user is a member of
-func printOrgs(tokenJSON HFTokenJSON) {
- color.Green("\n[i] Organizations:")
-
- if len(tokenJSON.Orgs) == 0 {
- color.Yellow("\tNo organizations found.")
- return
- }
-
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Name", "Role", "Is Enterprise"})
- for _, org := range tokenJSON.Orgs {
- enterprise := ""
- role := ""
- if org.IsEnterprise {
- enterprise = color.New(color.FgGreen).Sprint("True")
- } else {
- enterprise = "False"
- }
- if org.Role == "admin" {
- role = color.New(color.FgGreen).Sprint("Admin")
- } else {
- role = org.Role
- }
- t.AppendRow([]interface{}{color.GreenString(org.Name), role, enterprise})
- }
- t.Render()
-}
-
-// modelNameLookup is a helper function to lookup model name by _id
-func modelNameLookup(models []Model, id string) string {
- for _, model := range models {
- if model.ID == id {
- return model.Name
- }
- }
- return ""
-}
-
-// printAccessibleModels adds permissions as needed to each model
-//
-// and then calls the printModelsTable function
-func printAccessibleModels(allModels []Model, tokenJSON HFTokenJSON) {
- color.Green("\n[i] Accessible Models:")
-
- if tokenJSON.Auth.AccessToken.Type != FINEGRAINED {
- // Add Read Privs to All Models
- for idx := range allModels {
- allModels[idx].Permissions.Read = true
- }
- // Add Write Privs to All Models if Write Access
- if tokenJSON.Auth.AccessToken.Type == WRITE {
- for idx := range allModels {
- allModels[idx].Permissions.Write = true
- }
- }
- // Print Models Table
- printModelsTable(allModels)
- return
- }
-
- // finegrained scopes are grouped by org, user or model.
- // this section will extract the relevant permissions for each entity and store them in a map
- var nameToPermissions = make(map[string]Permissions)
- for _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {
- read := false
- write := false
- for _, perm := range permission.Permissions {
- if perm == "repo.content.read" {
- read = true
- } else if perm == "repo.write" {
- write = true
- }
- }
- if permission.Entity.Type == "user" || permission.Entity.Type == "org" {
- nameToPermissions[permission.Entity.Name] = Permissions{Read: read, Write: write}
- } else if permission.Entity.Type == "model" {
- nameToPermissions[modelNameLookup(allModels, permission.Entity.ID)] = Permissions{Read: read, Write: write}
- }
- }
-
- // apply permissions to all models
- for idx := range allModels {
- // get username/orgname for each model and apply those permissions
- modelUsername := strings.Split(allModels[idx].Name, "/")[0]
- if permissions, ok := nameToPermissions[modelUsername]; ok {
- allModels[idx].Permissions = permissions
- }
- // override model permissions with repo-specific permissions
- if permissions, ok := nameToPermissions[allModels[idx].Name]; ok {
- allModels[idx].Permissions = permissions
- }
- }
-
- // Print Models Table
- printModelsTable(allModels)
-}
-
-// printModelsTable prints the models table
-func printModelsTable(models []Model) {
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Model", "Private", "Read", "Write"})
- for _, model := range models {
- var name, read, write, private string
- if model.Permissions.Read {
- read = color.New(color.FgGreen).Sprint("True")
- } else {
- read = "False"
- }
- if model.Permissions.Write {
- write = color.New(color.FgGreen).Sprint("True")
- } else {
- write = "False"
- }
- if model.Private {
- private = color.New(color.FgGreen).Sprint("True")
- name = color.New(color.FgGreen).Sprint(model.Name)
- } else {
- private = "False"
- name = model.Name
- }
- t.AppendRow([]interface{}{name, private, read, write})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/huggingface/huggingface_test.go b/pkg/analyzer/analyzers/huggingface/huggingface_test.go
deleted file mode 100644
index eba22f70efd2..000000000000
--- a/pkg/analyzer/analyzers/huggingface/huggingface_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package huggingface
-
-import (
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid Huggingface key",
- key: testSecrets.MustGetField("HUGGINGFACE"),
- want: `{
- "AnalyzerType":6,
- "Bindings":[
- {
- "Resource":{
- "Name":"zubairkhan/test",
- "FullyQualifiedName": "huggingface.com/model/64d8220c0d879296892ab835",
- "Type":"model",
- "Metadata":{
- "private":false
- },
- "Parent":null
- },
- "Permission":{
- "Value":"Read",
- "Parent":null
- }
- },
- {
- "Resource":{
- "Name":"zubairkhan/first_repo",
- "FullyQualifiedName": "huggingface.com/model/64d82349a787c9bc7bbb2ab4",
- "Type":"model",
- "Metadata":{
- "private":true
- },
- "Parent":null
- },
- "Permission":{
- "Value":"Read",
- "Parent":null
- }
- }
- ],
- "UnboundedResources":null,
- "Metadata":{
- "name":"Zubair Khan",
- "token_name":"another_one",
- "token_type":"read",
- "username":"zubairkhan"
- }
- }`,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/huggingface/permissions.go b/pkg/analyzer/analyzers/huggingface/permissions.go
deleted file mode 100644
index 335a3fe345c9..000000000000
--- a/pkg/analyzer/analyzers/huggingface/permissions.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package huggingface
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Read Permission = iota
- Write Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Read: "read",
- Write: "write",
- }
-
- StringToPermission = map[string]Permission{
- "read": Read,
- "write": Write,
- }
-
- PermissionIDs = map[Permission]int{
- Read: 1,
- Write: 2,
- }
-
- IdToPermission = map[int]Permission{
- 1: Read,
- 2: Write,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/huggingface/permissions.yaml b/pkg/analyzer/analyzers/huggingface/permissions.yaml
deleted file mode 100644
index 19f566bfc82d..000000000000
--- a/pkg/analyzer/analyzers/huggingface/permissions.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-permissions:
- - read
- - write
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/huggingface/scopes.go b/pkg/analyzer/analyzers/huggingface/scopes.go
deleted file mode 100644
index 6067b99ff312..000000000000
--- a/pkg/analyzer/analyzers/huggingface/scopes.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package huggingface
-
-//nolint:unused
-var repo_scopes = map[string]string{
- "repo.content.read": "Read access to contents",
- "discussion.write": "Interact with discussions / Open pull requests",
- "repo.write": "Write access to contents/settings",
-}
-
-var org_scopes_order = []string{
- "Repos",
- "Collections",
- "Inference endpoints",
- "Org settings",
-}
-
-var org_scopes = map[string]map[string]string{
- "Repos": {
- "repo.content.read": "Read access to contents of all repos",
- "discussion.write": "Interact with discussions / Open pull requests on all repos",
- "repo.write": "Write access to contents/settings of all repos",
- },
- "Collections": {
- "collection.read": "Read access to all collections",
- "collection.write": "Write access to all collections",
- },
- "Inference endpoints": {
- "inference.endpoints.infer.write": "Make calls to inference endpoints",
- "inference.endpoints.write": "Manage inference endpoints",
- },
- "Org settings": {
- "org.read": "Read access to organization's settings",
- "org.write": "Write access to organization's settings / member management",
- },
-}
-
-var user_scopes_order = []string{
- "Billing",
- "Collections",
- "Discussions & Posts",
- "Inference",
- "Repos",
- "Webhooks",
-}
-
-var user_scopes = map[string]map[string]string{
- "Billing": {
- "user.billing.read": "Read access to user's billing usage",
- },
- "Collections": {
- "collection.read": "Read access to all collections under user's namespace",
- "collection.write": "Write access to all collections under user's namespace",
- },
- "Discussions & Posts": {
- // Note: prepending global. to scopes that are nested under "global" in fine-grained permissions JSON
- // otherwise they would overlap with user scopes under the "scoped" JSON
- "discussion.write": "Interact with discussions / Open pull requests on repos under user's namespace",
- "global.discussion.write": "Interact with discussions / Open pull requests on external repos",
- "global.post.write": "Interact with posts",
- },
- "Inference": {
- "global.inference.serverless.write": "Make calls to the serverless Inference API",
- "inference.endpoints.infer.write": "Make calls to inference endpoints",
- "inference.endpoints.write": "Manage inference endpoints",
- },
- "Repos": {
- "repo.content.read": "Read access to contents of all repos under user's namespace",
- "repo.write": "Write access to contents/settings of all repos under user's namespace",
- },
- "Webhooks": {
- "user.webhooks.read": "Access webhooks data",
- "user.webhooks.write": "Create and manage webhooks",
- },
-}
diff --git a/pkg/analyzer/analyzers/jira/jira.go b/pkg/analyzer/analyzers/jira/jira.go
deleted file mode 100644
index 3caba5bdbe3b..000000000000
--- a/pkg/analyzer/analyzers/jira/jira.go
+++ /dev/null
@@ -1,201 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go jira
-
-package jira
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "slices"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeJira
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- token, exist := credInfo["token"]
- if !exist {
- return nil, fmt.Errorf("token not found in credential info")
- }
- domain, exist := credInfo["domain"]
- if !exist {
- return nil, fmt.Errorf("domain not found in credential info")
- }
- email, exist := credInfo["email"]
- if !exist {
- return nil, fmt.Errorf("email not found in credential info")
- }
-
- info, err := AnalyzePermissions(a.Cfg, token, domain, email)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, domain, email, token string) {
- info, err := AnalyzePermissions(cfg, token, domain, email)
- if err != nil {
- // just print the error in cli and continue as a partial success
- color.Red("[x] Error : %s", err.Error())
- } else {
- color.Green("[!] Valid Jira API token\n\n")
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- printUserInfo(info.UserInfo)
- printPermissions(info.Permissions)
- printResources(info.Resources)
-}
-
-func AnalyzePermissions(cfg *config.Config, token, domain, email string) (*SecretInfo, error) {
- // create http client
- client := analyzers.NewAnalyzeClient(cfg)
-
- var secretInfo = &SecretInfo{}
-
- // capture the user information
- if err := captureUserInfo(client, token, domain, email, secretInfo); err != nil {
- return nil, err
- }
-
- body, _, err := capturePermissions(client, domain, email, token)
- if err != nil {
- return secretInfo, fmt.Errorf("failed to check permissions: %w", err)
- }
-
- var permissionsResp JiraPermissionsResponse
- if err := json.Unmarshal(body, &permissionsResp); err != nil {
- return secretInfo, fmt.Errorf("failed to unmarshal permissions response: %w", err)
- }
-
- var grantedPermissions []string
- for key, perm := range permissionsResp.Permissions {
- if perm.HavePermission {
- grantedPermissions = append(grantedPermissions, key)
- }
- }
- slices.Sort(grantedPermissions)
- secretInfo.Permissions = grantedPermissions
-
- // capture the resources
- if err := captureResources(client, domain, email, token, secretInfo, grantedPermissions); err != nil {
- // return secretInfo as well in case of error for partial success
- return secretInfo, err
- }
-
- return secretInfo, nil
-}
-
-// secretInfoToAnalyzerResult translate secret info to Analyzer Result
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeJira,
- Metadata: map[string]any{},
- Bindings: make([]analyzers.Binding, 0),
- }
-
- for _, resource := range info.Resources {
- for _, perm := range resource.Permissions {
- binding := analyzers.Binding{
- Resource: *secretInfoResourceToAnalyzerResource(resource),
- Permission: analyzers.Permission{
- Value: perm,
- },
- }
-
- if resource.Parent != nil {
- binding.Resource.Parent = secretInfoResourceToAnalyzerResource(*resource.Parent)
- }
-
- result.Bindings = append(result.Bindings, binding)
- }
- }
-
- return &result
-}
-
-// secretInfoResourceToAnalyzerResource translate secret info resource to analyzer resource for binding
-func secretInfoResourceToAnalyzerResource(resource JiraResource) *analyzers.Resource {
- analyzerRes := analyzers.Resource{
- // make fully qualified name unique
- FullyQualifiedName: resource.Type + "/" + resource.ID,
- Name: resource.Name,
- Type: resource.Type,
- Metadata: map[string]any{},
- }
-
- for key, value := range resource.Metadata {
- analyzerRes.Metadata[key] = value
- }
-
- return &analyzerRes
-}
-
-// cli print functions
-func printUserInfo(user JiraUser) {
- if user.AccountID == "" {
- color.Red("[x] No user information found")
- return
- }
- color.Yellow("[i] User Information:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"ID", "Name", "Account Type", "Email", "Active"})
- t.AppendRow(table.Row{color.GreenString(user.AccountID), color.GreenString(user.DisplayName), color.GreenString(user.AccountType), color.GreenString(user.EmailAddress), color.GreenString(fmt.Sprintf("%t", user.Active))})
-
- t.Render()
-}
-
-func printPermissions(permissions []string) {
- if len(permissions) == 0 {
- color.Red("[x] No permissions found")
- return
- }
- color.Yellow("[i] Permissions:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission"})
- for _, scope := range permissions {
- t.AppendRow(table.Row{color.GreenString(scope)})
- }
- t.Render()
-}
-
-func printResources(resources []JiraResource) {
- if len(resources) == 0 {
- color.Red("[x] No resources found")
- return
- }
- color.Yellow("[i] Resources:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Name", "Type"})
- for _, resource := range resources {
- t.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})
- }
-
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/jira/jira_test.go b/pkg/analyzer/analyzers/jira/jira_test.go
deleted file mode 100644
index 879eb5246bca..000000000000
--- a/pkg/analyzer/analyzers/jira/jira_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package jira
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed result_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "analyzers1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- jiraDomain := testSecrets.MustGetField("JIRA_DOMAIN_ANALYZE")
- jiraEmail := testSecrets.MustGetField("JIRA_EMAIL_ANALYZE")
- jiraToken := testSecrets.MustGetField("JIRA_TOKEN_ANALYZE")
-
- tests := []struct {
- name string
- domain string
- email string
- token string
- want []byte
- wantErr bool
- }{
- {
- name: "valid jira token",
- domain: jiraDomain,
- email: jiraEmail,
- token: jiraToken,
- want: expectedOutput,
- wantErr: false,
- },
- {
- name: "invalid jira token",
- domain: jiraDomain,
- email: jiraEmail,
- token: "invalid",
- want: nil,
- wantErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"token": tt.token, "domain": tt.domain, "email": tt.email})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- if tt.wantErr {
- if got != nil {
- t.Errorf("Analyzer.Analyze() got = %v, want nil", got)
- }
- return
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.FullyQualifiedName == bindings[j].Resource.FullyQualifiedName {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.FullyQualifiedName < bindings[j].Resource.FullyQualifiedName
- })
-}
diff --git a/pkg/analyzer/analyzers/jira/models.go b/pkg/analyzer/analyzers/jira/models.go
deleted file mode 100644
index 6d46c8033ac7..000000000000
--- a/pkg/analyzer/analyzers/jira/models.go
+++ /dev/null
@@ -1,238 +0,0 @@
-package jira
-
-import (
- "sync"
-)
-
-const (
- ResourceTypeProject = "Project"
- ResourceTypeBoard = "Board"
- ResourceTypeGroup = "Group"
- ResourceTypeIssue = "Issue"
- ResourceTypeUser = "User"
- ResourceTypeAuditRecord = "AuditRecord"
-)
-
-var ResourcePermissions = map[string][]Permission{
- ResourceTypeProject: {
- Administer,
- BrowseProjects,
- AdministerProjects,
- CreateProject,
- EditIssueLayout,
- ViewDevTools,
- ViewAggregatedData,
- SystemAdmin,
- },
- ResourceTypeIssue: {
- Administer,
- AddComments,
- AssignIssues,
- CloseIssues,
- CreateAttachments,
- CreateIssues,
- DeleteIssues,
- DeleteAllAttachments,
- DeleteAllComments,
- DeleteAllWorklogs,
- DeleteOwnAttachments,
- DeleteOwnComments,
- DeleteOwnWorklogs,
- EditAllComments,
- EditAllWorklogs,
- EditIssues,
- EditOwnComments,
- EditOwnWorklogs,
- LinkIssues,
- ManageWatchers,
- ModifyReporter,
- MoveIssues,
- ResolveIssues,
- ScheduleIssues,
- SetIssueSecurity,
- SystemAdmin,
- TransitionIssues,
- UnarchiveIssues,
- ViewVotersAndWatchers,
- WorkOnIssues,
- },
- ResourceTypeBoard: {
- Administer,
- ManageSprintsPermission,
- BrowseProjects,
- SystemAdmin,
- ViewAggregatedData,
- },
- ResourceTypeUser: {
- AssignableUser,
- SystemAdmin,
- UserPicker,
- },
- ResourceTypeGroup: {
- Administer,
- SystemAdmin,
- },
- ResourceTypeAuditRecord: {
- Administer,
- SystemAdmin,
- },
-}
-
-type SecretInfo struct {
- mu sync.RWMutex
-
- UserInfo JiraUser
- Permissions []string
- Resources []JiraResource
-}
-
-// JiraUser represents the response from /myself API
-type JiraUser struct {
- AccountID string `json:"accountId"`
- AccountType string `json:"accountType"`
- DisplayName string `json:"displayName"`
- EmailAddress string `json:"emailAddress"`
- Active bool `json:"active"`
- TimeZone string `json:"timeZone"`
- Locale string `json:"locale"`
- Self string `json:"self"`
-}
-
-type JiraResource struct {
- ID string
- Name string
- Type string
- Metadata map[string]string
- Parent *JiraResource
- Permissions []string
-}
-
-func (s *SecretInfo) appendResource(resource JiraResource, resourceType string) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- if perms, ok := ResourcePermissions[resourceType]; ok {
- for _, p := range perms {
- if userPerms[p] {
- resource.Permissions = append(resource.Permissions, PermissionStrings[p])
- }
- }
- }
-
- s.Resources = append(s.Resources, resource)
-}
-
-type JiraPermissionsResponse struct {
- Permissions map[string]JiraPermission `json:"permissions"`
-}
-
-type JiraPermission struct {
- ID string `json:"id"`
- Key string `json:"key"`
- Name string `json:"name"`
- Type string `json:"type"`
- Description string `json:"description"`
- HavePermission bool `json:"havePermission"`
-}
-
-type ProjectSearchResponse struct {
- MaxResults int `json:"maxResults"`
- Total int `json:"total"`
- IsLast bool `json:"isLast"`
- Values []JiraProject `json:"values"`
-}
-
-type JiraProject struct {
- ID string `json:"id"`
- Key string `json:"key"`
- Name string `json:"name"`
- ProjectTypeKey string `json:"projectTypeKey"`
- IsPrivate bool `json:"isPrivate"`
- UUID string `json:"uuid"`
-}
-
-type JiraIssue struct {
- Issues []struct {
- ID string `json:"id"`
- Key string `json:"key"`
- Fields struct {
- Summary string `json:"summary"`
- Status struct {
- Name string `json:"name"`
- } `json:"status"`
- IssueType struct {
- Name string `json:"name"`
- } `json:"issuetype"`
- } `json:"fields"`
- } `json:"issues"`
-}
-
-type JiraBoard struct {
- Values []struct {
- ID int `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"`
- Self string `json:"self"`
- IsPrivate bool `json:"isPrivate"`
- Location struct {
- ProjectID int `json:"projectId"`
- DisplayName string `json:"displayName"`
- ProjectName string `json:"projectName"`
- ProjectKey string `json:"projectKey"`
- ProjectTypeKey string `json:"projectTypeKey"`
- AvatarURI string `json:"avatarURI"`
- Name string `json:"name"`
- } `json:"location"`
- } `json:"values"`
-}
-
-type JiraGroup struct {
- Total int `json:"total"`
- Groups []struct {
- Name string `json:"name"`
- HTML string `json:"html"`
- GroupID string `json:"groupId"`
- Labels []struct {
- Text string `json:"text"`
- Title string `json:"title"`
- Type string `json:"type"`
- } `json:"labels"`
- } `json:"groups"`
-}
-
-type AuditRecord struct {
- Offset int `json:"offset"`
- Limit int `json:"limit"`
- Total int `json:"total"`
- Records []struct {
- ID int `json:"id"`
- Summary string `json:"summary"`
- Created string `json:"created"`
- Category string `json:"category"`
- EventSource string `json:"eventSource"`
- RemoteAddress string `json:"remoteAddress,omitempty"`
- AuthorKey string `json:"authorKey,omitempty"`
- AuthorAccount string `json:"authorAccountId,omitempty"`
-
- ObjectItem struct {
- ID string `json:"id,omitempty"`
- Name string `json:"name"`
- TypeName string `json:"typeName"`
- ParentID string `json:"parentId,omitempty"`
- ParentName string `json:"parentName,omitempty"`
- } `json:"objectItem"`
-
- AssociatedItems []struct {
- ID string `json:"id"`
- Name string `json:"name"`
- TypeName string `json:"typeName"`
- ParentID string `json:"parentId"`
- ParentName string `json:"parentName"`
- } `json:"associatedItems"`
-
- ChangedValues []struct {
- FieldName string `json:"fieldName"`
- ChangedTo string `json:"changedTo"`
- } `json:"changedValues"`
- } `json:"records"`
-}
diff --git a/pkg/analyzer/analyzers/jira/permissions.go b/pkg/analyzer/analyzers/jira/permissions.go
deleted file mode 100644
index 644bcab90dd6..000000000000
--- a/pkg/analyzer/analyzers/jira/permissions.go
+++ /dev/null
@@ -1,276 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package jira
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- AddComments Permission = iota
- Administer Permission = iota
- AdministerProjects Permission = iota
- AssignableUser Permission = iota
- AssignIssues Permission = iota
- BrowseProjects Permission = iota
- BulkChange Permission = iota
- CloseIssues Permission = iota
- CreateAttachments Permission = iota
- CreateIssues Permission = iota
- CreateProject Permission = iota
- CreateSharedObjects Permission = iota
- DeleteAllAttachments Permission = iota
- DeleteAllComments Permission = iota
- DeleteAllWorklogs Permission = iota
- DeleteIssues Permission = iota
- DeleteOwnAttachments Permission = iota
- DeleteOwnComments Permission = iota
- DeleteOwnWorklogs Permission = iota
- EditAllComments Permission = iota
- EditAllWorklogs Permission = iota
- EditIssues Permission = iota
- EditIssueLayout Permission = iota
- EditOwnComments Permission = iota
- EditOwnWorklogs Permission = iota
- EditWorkflow Permission = iota
- LinkIssues Permission = iota
- ManageGroupFilterSubscriptions Permission = iota
- ManageSprintsPermission Permission = iota
- ManageWatchers Permission = iota
- ModifyReporter Permission = iota
- MoveIssues Permission = iota
- ResolveIssues Permission = iota
- ScheduleIssues Permission = iota
- SetIssueSecurity Permission = iota
- SystemAdmin Permission = iota
- TransitionIssues Permission = iota
- UnarchiveIssues Permission = iota
- UserPicker Permission = iota
- ViewAggregatedData Permission = iota
- ViewDevTools Permission = iota
- ViewReadonlyWorkflow Permission = iota
- ViewVotersAndWatchers Permission = iota
- WorkOnIssues Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- AddComments: "add_comments",
- Administer: "administer",
- AdministerProjects: "administer_projects",
- AssignableUser: "assignable_user",
- AssignIssues: "assign_issues",
- BrowseProjects: "browse_projects",
- BulkChange: "bulk_change",
- CloseIssues: "close_issues",
- CreateAttachments: "create_attachments",
- CreateIssues: "create_issues",
- CreateProject: "create_project",
- CreateSharedObjects: "create_shared_objects",
- DeleteAllAttachments: "delete_all_attachments",
- DeleteAllComments: "delete_all_comments",
- DeleteAllWorklogs: "delete_all_worklogs",
- DeleteIssues: "delete_issues",
- DeleteOwnAttachments: "delete_own_attachments",
- DeleteOwnComments: "delete_own_comments",
- DeleteOwnWorklogs: "delete_own_worklogs",
- EditAllComments: "edit_all_comments",
- EditAllWorklogs: "edit_all_worklogs",
- EditIssues: "edit_issues",
- EditIssueLayout: "edit_issue_layout",
- EditOwnComments: "edit_own_comments",
- EditOwnWorklogs: "edit_own_worklogs",
- EditWorkflow: "edit_workflow",
- LinkIssues: "link_issues",
- ManageGroupFilterSubscriptions: "manage_group_filter_subscriptions",
- ManageSprintsPermission: "manage_sprints_permission",
- ManageWatchers: "manage_watchers",
- ModifyReporter: "modify_reporter",
- MoveIssues: "move_issues",
- ResolveIssues: "resolve_issues",
- ScheduleIssues: "schedule_issues",
- SetIssueSecurity: "set_issue_security",
- SystemAdmin: "system_admin",
- TransitionIssues: "transition_issues",
- UnarchiveIssues: "unarchive_issues",
- UserPicker: "user_picker",
- ViewAggregatedData: "view_aggregated_data",
- ViewDevTools: "view_dev_tools",
- ViewReadonlyWorkflow: "view_readonly_workflow",
- ViewVotersAndWatchers: "view_voters_and_watchers",
- WorkOnIssues: "work_on_issues",
- }
-
- StringToPermission = map[string]Permission{
- "add_comments": AddComments,
- "administer": Administer,
- "administer_projects": AdministerProjects,
- "assignable_user": AssignableUser,
- "assign_issues": AssignIssues,
- "browse_projects": BrowseProjects,
- "bulk_change": BulkChange,
- "close_issues": CloseIssues,
- "create_attachments": CreateAttachments,
- "create_issues": CreateIssues,
- "create_project": CreateProject,
- "create_shared_objects": CreateSharedObjects,
- "delete_all_attachments": DeleteAllAttachments,
- "delete_all_comments": DeleteAllComments,
- "delete_all_worklogs": DeleteAllWorklogs,
- "delete_issues": DeleteIssues,
- "delete_own_attachments": DeleteOwnAttachments,
- "delete_own_comments": DeleteOwnComments,
- "delete_own_worklogs": DeleteOwnWorklogs,
- "edit_all_comments": EditAllComments,
- "edit_all_worklogs": EditAllWorklogs,
- "edit_issues": EditIssues,
- "edit_issue_layout": EditIssueLayout,
- "edit_own_comments": EditOwnComments,
- "edit_own_worklogs": EditOwnWorklogs,
- "edit_workflow": EditWorkflow,
- "link_issues": LinkIssues,
- "manage_group_filter_subscriptions": ManageGroupFilterSubscriptions,
- "manage_sprints_permission": ManageSprintsPermission,
- "manage_watchers": ManageWatchers,
- "modify_reporter": ModifyReporter,
- "move_issues": MoveIssues,
- "resolve_issues": ResolveIssues,
- "schedule_issues": ScheduleIssues,
- "set_issue_security": SetIssueSecurity,
- "system_admin": SystemAdmin,
- "transition_issues": TransitionIssues,
- "unarchive_issues": UnarchiveIssues,
- "user_picker": UserPicker,
- "view_aggregated_data": ViewAggregatedData,
- "view_dev_tools": ViewDevTools,
- "view_readonly_workflow": ViewReadonlyWorkflow,
- "view_voters_and_watchers": ViewVotersAndWatchers,
- "work_on_issues": WorkOnIssues,
- }
-
- PermissionIDs = map[Permission]int{
- AddComments: 1,
- Administer: 2,
- AdministerProjects: 3,
- AssignableUser: 4,
- AssignIssues: 5,
- BrowseProjects: 6,
- BulkChange: 7,
- CloseIssues: 8,
- CreateAttachments: 9,
- CreateIssues: 10,
- CreateProject: 11,
- CreateSharedObjects: 12,
- DeleteAllAttachments: 13,
- DeleteAllComments: 14,
- DeleteAllWorklogs: 15,
- DeleteIssues: 16,
- DeleteOwnAttachments: 17,
- DeleteOwnComments: 18,
- DeleteOwnWorklogs: 19,
- EditAllComments: 20,
- EditAllWorklogs: 21,
- EditIssues: 22,
- EditIssueLayout: 23,
- EditOwnComments: 24,
- EditOwnWorklogs: 25,
- EditWorkflow: 26,
- LinkIssues: 27,
- ManageGroupFilterSubscriptions: 28,
- ManageSprintsPermission: 29,
- ManageWatchers: 30,
- ModifyReporter: 31,
- MoveIssues: 32,
- ResolveIssues: 33,
- ScheduleIssues: 34,
- SetIssueSecurity: 35,
- SystemAdmin: 36,
- TransitionIssues: 37,
- UnarchiveIssues: 38,
- UserPicker: 39,
- ViewAggregatedData: 40,
- ViewDevTools: 41,
- ViewReadonlyWorkflow: 42,
- ViewVotersAndWatchers: 43,
- WorkOnIssues: 44,
- }
-
- IdToPermission = map[int]Permission{
- 1: AddComments,
- 2: Administer,
- 3: AdministerProjects,
- 4: AssignableUser,
- 5: AssignIssues,
- 6: BrowseProjects,
- 7: BulkChange,
- 8: CloseIssues,
- 9: CreateAttachments,
- 10: CreateIssues,
- 11: CreateProject,
- 12: CreateSharedObjects,
- 13: DeleteAllAttachments,
- 14: DeleteAllComments,
- 15: DeleteAllWorklogs,
- 16: DeleteIssues,
- 17: DeleteOwnAttachments,
- 18: DeleteOwnComments,
- 19: DeleteOwnWorklogs,
- 20: EditAllComments,
- 21: EditAllWorklogs,
- 22: EditIssues,
- 23: EditIssueLayout,
- 24: EditOwnComments,
- 25: EditOwnWorklogs,
- 26: EditWorkflow,
- 27: LinkIssues,
- 28: ManageGroupFilterSubscriptions,
- 29: ManageSprintsPermission,
- 30: ManageWatchers,
- 31: ModifyReporter,
- 32: MoveIssues,
- 33: ResolveIssues,
- 34: ScheduleIssues,
- 35: SetIssueSecurity,
- 36: SystemAdmin,
- 37: TransitionIssues,
- 38: UnarchiveIssues,
- 39: UserPicker,
- 40: ViewAggregatedData,
- 41: ViewDevTools,
- 42: ViewReadonlyWorkflow,
- 43: ViewVotersAndWatchers,
- 44: WorkOnIssues,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/jira/permissions.yaml b/pkg/analyzer/analyzers/jira/permissions.yaml
deleted file mode 100644
index 720646284c23..000000000000
--- a/pkg/analyzer/analyzers/jira/permissions.yaml
+++ /dev/null
@@ -1,45 +0,0 @@
-permissions:
- - add_comments
- - administer
- - administer_projects
- - assignable_user
- - assign_issues
- - browse_projects
- - bulk_change
- - close_issues
- - create_attachments
- - create_issues
- - create_project
- - create_shared_objects
- - delete_all_attachments
- - delete_all_comments
- - delete_all_worklogs
- - delete_issues
- - delete_own_attachments
- - delete_own_comments
- - delete_own_worklogs
- - edit_all_comments
- - edit_all_worklogs
- - edit_issues
- - edit_issue_layout
- - edit_own_comments
- - edit_own_worklogs
- - edit_workflow
- - link_issues
- - manage_group_filter_subscriptions
- - manage_sprints_permission
- - manage_watchers
- - modify_reporter
- - move_issues
- - resolve_issues
- - schedule_issues
- - set_issue_security
- - system_admin
- - transition_issues
- - unarchive_issues
- - user_picker
- - view_aggregated_data
- - view_dev_tools
- - view_readonly_workflow
- - view_voters_and_watchers
- - work_on_issues
diff --git a/pkg/analyzer/analyzers/jira/requests.go b/pkg/analyzer/analyzers/jira/requests.go
deleted file mode 100644
index bac24b9133e7..000000000000
--- a/pkg/analyzer/analyzers/jira/requests.go
+++ /dev/null
@@ -1,442 +0,0 @@
-package jira
-
-import (
- "encoding/base64"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "sync"
-)
-
-type endpoint int
-
-const (
- // list of endpoints
- mySelf endpoint = iota
- myPermissions
- getAllProjects
- searchIssues
- getAllBoards
- getAllUsers
- findGroups
- getAuditRecords
-)
-
-var (
- baseURL = "https://%s/rest"
-
- // endpoints contain Jira API endpoints
- endpoints = map[endpoint]string{
- mySelf: "myself",
- myPermissions: "mypermissions",
- searchIssues: "search/jql",
- getAllProjects: "project/search",
- getAllBoards: "board",
- getAllUsers: "users/search",
- findGroups: "groups/picker",
- getAuditRecords: "auditing/record",
- }
-
- userPerms = make(map[Permission]bool)
-)
-
-// buildBasicAuthHeader constructs the Basic Auth header
-func buildBasicAuthHeader(email, token string) string {
- auth := fmt.Sprintf("%s:%s", email, token)
- return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
-}
-
-// makeJiraRequest send the API request to passed url with passed key as API Key and return response body and status code
-func makeJiraRequest(client *http.Client, endpoint, email, token string) ([]byte, int, error) {
- // create request
- req, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody)
- if err != nil {
- return nil, 0, err
- }
-
- req.Header.Set("Accept", "application/json")
- req.Header.Set("Authorization", buildBasicAuthHeader(email, token))
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, 0, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- responseBodyByte, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, 0, err
- }
-
- return responseBodyByte, resp.StatusCode, nil
-}
-
-func capturePermissions(client *http.Client, domain, email, token string) ([]byte, int, error) {
- var allPermissions []string
- for _, key := range PermissionStrings {
- allPermissions = append(allPermissions, strings.ToUpper(key))
- }
-
- query := url.Values{}
- query.Set("permissions", strings.Join(allPermissions, ","))
-
- endpoint := fmt.Sprintf("%s/api/3/%s?%s", fmt.Sprintf(baseURL, domain), endpoints[myPermissions], query.Encode())
-
- return makeJiraRequest(client, endpoint, email, token)
-}
-
-// captureResources try to capture all the resource that the key can access
-func captureResources(client *http.Client, domain, email, token string, secretInfo *SecretInfo, grantedPermissions []string) error {
- for _, p := range grantedPermissions {
- userPerms[StringToPermission[strings.ToLower(p)]] = true
- }
-
- var (
- wg sync.WaitGroup
- errAggWg sync.WaitGroup
- aggregatedErrs = make([]error, 0)
- errChan = make(chan error, 1)
- )
-
- errAggWg.Add(1)
- go func() {
- defer errAggWg.Done()
- for err := range errChan {
- aggregatedErrs = append(aggregatedErrs, err)
- }
- }()
-
- launchTask := func(task func() error) {
- wg.Add(1)
- go func() {
- defer wg.Done()
- if err := task(); err != nil {
- errChan <- err
- }
- }()
- }
-
- projects, err := captureProjects(client, domain, email, token, secretInfo)
- if err != nil {
- return fmt.Errorf("failed to capture projects: %w", err)
- }
- if projects != nil {
- for _, proj := range projects.Values {
- launchTask(func() error {
- return captureIssues(client, domain, email, token, proj.Key, secretInfo)
- })
- }
- }
- launchTask(func() error { return captureBoards(client, domain, email, token, secretInfo) })
- launchTask(func() error { return captureUsers(client, domain, email, token, secretInfo) })
- launchTask(func() error { return captureGroups(client, domain, email, token, secretInfo) })
- launchTask(func() error { return captureAuditLogs(client, domain, email, token, secretInfo) })
-
- wg.Wait()
- close(errChan)
- errAggWg.Wait()
-
- if len(aggregatedErrs) > 0 {
- return errors.Join(aggregatedErrs...)
- }
-
- return nil
-}
-
-// captureUserInfo calls `/myself` API and store the current user information in secretInfo
-func captureUserInfo(client *http.Client, token, domain, email string, secretInfo *SecretInfo) error {
- endPoint := fmt.Sprintf("%s/api/3/%s", fmt.Sprintf(baseURL, domain), endpoints[mySelf])
- respBody, statusCode, err := makeJiraRequest(client, endPoint, email, token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var user JiraUser
-
- if err := json.Unmarshal(respBody, &user); err != nil {
- return err
- }
-
- secretInfo.UserInfo = user
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return fmt.Errorf("invalid email or api token")
- case http.StatusNotFound:
- return fmt.Errorf("domain not found: %s", domain)
- default:
- return fmt.Errorf("unexpected status code: %d for API: %s", statusCode, endpoints[mySelf])
- }
-}
-
-func captureProjects(client *http.Client, domain, email, token string, secretInfo *SecretInfo) (*ProjectSearchResponse, error) {
- endpoint := fmt.Sprintf("%s/api/3/%s", fmt.Sprintf(baseURL, domain), endpoints[getAllProjects])
- body, statusCode, err := makeJiraRequest(client, endpoint, email, token)
- if err != nil {
- return nil, err
- }
-
- if err := handleStatusCode(statusCode, endpoint); err != nil {
- return nil, err
- }
-
- var resp ProjectSearchResponse
- if err := json.Unmarshal(body, &resp); err != nil {
- return nil, fmt.Errorf("failed to unmarshal project response: %w", err)
- }
-
- for _, proj := range resp.Values {
- resource := JiraResource{
- ID: proj.ID,
- Name: proj.Name,
- Type: ResourceTypeProject,
- Metadata: map[string]string{
- "Key": proj.Key,
- "UUID": proj.UUID,
- "Private": strconv.FormatBool(proj.IsPrivate),
- "TypeKey": proj.ProjectTypeKey,
- },
- }
-
- secretInfo.appendResource(resource, ResourceTypeProject)
- }
-
- return &resp, nil
-}
-
-func captureIssues(client *http.Client, domain, email, token, projectKey string, secretInfo *SecretInfo) error {
- path := fmt.Sprintf("api/3/%s", endpoints[searchIssues])
- query := fmt.Sprintf("jql=project=%s&fields=issuetype,summary,status", projectKey)
- endpoint := fmt.Sprintf("%s/%s?%s", fmt.Sprintf(baseURL, domain), path, query)
-
- body, statusCode, err := makeJiraRequest(client, endpoint, email, token)
- if err != nil {
- return err
- }
-
- if err := handleStatusCode(statusCode, endpoint); err != nil {
- return err
- }
-
- var issueResp JiraIssue
- if err := json.Unmarshal(body, &issueResp); err != nil {
- return fmt.Errorf("failed to unmarshal issue response: %w", err)
- }
-
- for _, issue := range issueResp.Issues {
- issueResource := JiraResource{
- ID: issue.ID,
- Name: issue.Key,
- Type: issue.Fields.IssueType.Name,
- Metadata: map[string]string{
- "Summary": issue.Fields.Summary,
- "Status": issue.Fields.Status.Name,
- "Project": projectKey,
- },
- }
-
- secretInfo.appendResource(issueResource, ResourceTypeIssue)
- }
-
- return nil
-}
-
-func captureBoards(client *http.Client, domain, email, token string, secretInfo *SecretInfo) error {
- endpoint := fmt.Sprintf("%s/agile/1.0/%s", fmt.Sprintf(baseURL, domain), endpoints[getAllBoards])
-
- body, statusCode, err := makeJiraRequest(client, endpoint, email, token)
- if err != nil {
- return err
- }
-
- if err := handleStatusCode(statusCode, endpoint); err != nil {
- return err
- }
-
- var boardResp JiraBoard
- if err := json.Unmarshal(body, &boardResp); err != nil {
- return fmt.Errorf("failed to unmarshal board response: %w", err)
- }
-
- for _, board := range boardResp.Values {
- boardResource := JiraResource{
- ID: fmt.Sprintf("%d", board.ID),
- Name: board.Name,
- Type: ResourceTypeBoard,
- Metadata: map[string]string{
- "BoardType": board.Type,
- "IsPrivate": strconv.FormatBool(board.IsPrivate),
- "ProjectID": fmt.Sprintf("%d", board.Location.ProjectID),
- "ProjectKey": board.Location.ProjectKey,
- "ProjectName": board.Location.ProjectName,
- "ProjectType": board.Location.ProjectTypeKey,
- "DisplayName": board.Location.DisplayName,
- "AvatarURI": board.Location.AvatarURI,
- "BoardSelfURL": board.Self,
- },
- }
- secretInfo.appendResource(boardResource, ResourceTypeBoard)
- }
-
- return nil
-}
-
-func captureUsers(client *http.Client, domain, email, token string, secretInfo *SecretInfo) error {
- endpoint := fmt.Sprintf("%s/api/3/%s", fmt.Sprintf(baseURL, domain), endpoints[getAllUsers])
-
- body, statusCode, err := makeJiraRequest(client, endpoint, email, token)
- if err != nil {
- return err
- }
-
- if err := handleStatusCode(statusCode, endpoint); err != nil {
- return err
- }
-
- var users []JiraUser
- if err := json.Unmarshal(body, &users); err != nil {
- return fmt.Errorf("failed to unmarshal user response: %w", err)
- }
-
- for _, user := range users {
- userResource := JiraResource{
- ID: user.AccountID,
- Name: user.DisplayName,
- Type: ResourceTypeUser,
- Metadata: map[string]string{
- "Email": user.EmailAddress,
- "AccountType": user.AccountType,
- "Active": strconv.FormatBool(user.Active),
- "SelfURL": user.Self,
- },
- }
- if user.AccountType != "app" {
- secretInfo.appendResource(userResource, ResourceTypeUser)
- }
-
- }
-
- return nil
-}
-
-func captureGroups(client *http.Client, domain, email, token string, secretInfo *SecretInfo) error {
- endpoint := fmt.Sprintf("%s/api/3/%s", fmt.Sprintf(baseURL, domain), endpoints[findGroups])
-
- body, statusCode, err := makeJiraRequest(client, endpoint, email, token)
- if err != nil {
- return err
- }
-
- if err := handleStatusCode(statusCode, endpoint); err != nil {
- return err
- }
-
- var groupResp JiraGroup
- if err := json.Unmarshal(body, &groupResp); err != nil {
- return fmt.Errorf("failed to unmarshal group response: %w", err)
- }
-
- for _, group := range groupResp.Groups {
- metadata := map[string]string{
- "HTML": group.HTML,
- }
- if len(group.Labels) > 0 {
- for i, label := range group.Labels {
- metadata[fmt.Sprintf("Label%d_Text", i)] = label.Text
- metadata[fmt.Sprintf("Label%d_Title", i)] = label.Title
- metadata[fmt.Sprintf("Label%d_Type", i)] = label.Type
- }
- }
-
- groupResource := JiraResource{
- ID: group.GroupID,
- Name: group.Name,
- Type: ResourceTypeGroup,
- Metadata: metadata,
- }
-
- secretInfo.appendResource(groupResource, ResourceTypeGroup)
- }
-
- return nil
-}
-
-func captureAuditLogs(client *http.Client, domain, email, token string, secretInfo *SecretInfo) error {
- endpoint := fmt.Sprintf("%s/api/3/%s", fmt.Sprintf(baseURL, domain), endpoints[getAuditRecords])
-
- body, statusCode, err := makeJiraRequest(client, endpoint, email, token)
- if err != nil {
- return err
- }
-
- if err := handleStatusCode(statusCode, endpoint); err != nil {
- return err
- }
-
- var auditResp AuditRecord
- if err := json.Unmarshal(body, &auditResp); err != nil {
- return fmt.Errorf("failed to unmarshal audit logs: %w", err)
- }
-
- for _, record := range auditResp.Records {
- metadata := map[string]string{
- "Summary": record.Summary,
- "Created": record.Created,
- "Category": record.Category,
- "Type": record.ObjectItem.TypeName,
- "Object": record.ObjectItem.Name,
- }
-
- if record.AuthorAccount != "" {
- metadata["AuthorAccountID"] = record.AuthorAccount
- }
- if record.RemoteAddress != "" {
- metadata["RemoteAddress"] = record.RemoteAddress
- }
-
- for i, item := range record.AssociatedItems {
- metadata[fmt.Sprintf("AssociatedItem%d_Name", i)] = item.Name
- metadata[fmt.Sprintf("AssociatedItem%d_Type", i)] = item.TypeName
- }
-
- for i, change := range record.ChangedValues {
- metadata[fmt.Sprintf("ChangedField%d_Name", i)] = change.FieldName
- metadata[fmt.Sprintf("ChangedField%d_To", i)] = change.ChangedTo
- }
-
- resource := JiraResource{
- ID: fmt.Sprintf("%d", record.ID),
- Name: record.Summary,
- Type: ResourceTypeAuditRecord,
- Metadata: metadata,
- }
-
- secretInfo.appendResource(resource, ResourceTypeAuditRecord)
- }
-
- return nil
-}
-
-func handleStatusCode(statusCode int, endpoint string) error {
- switch {
- case statusCode == http.StatusOK:
- return nil
- case statusCode == http.StatusBadRequest:
- return fmt.Errorf("bad request for API: %s", endpoint)
- case statusCode == http.StatusUnauthorized, statusCode == http.StatusForbidden,
- statusCode == http.StatusNotFound, statusCode == http.StatusConflict:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d for API: %s", statusCode, endpoint)
- }
-}
diff --git a/pkg/analyzer/analyzers/jira/result_output.json b/pkg/analyzer/analyzers/jira/result_output.json
deleted file mode 100644
index bf1650a30bd2..000000000000
--- a/pkg/analyzer/analyzers/jira/result_output.json
+++ /dev/null
@@ -1,9470 +0,0 @@
-{
- "AnalyzerType": 42,
- "Bindings": [
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10000",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "development",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Includes development summary panel information used in JQL",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Dev Summary Custom Field",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "development",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10001",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Team",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Team",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Team",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10002",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Organizations",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Stores the organizations that are associated with a Service Desk customer portal requests. This custom field is created programmatically and required by Service Desk.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Organizations",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Organizations",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10003",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Approvers",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Contains users needed for approval. This custom field was created by Jira Service Desk.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "User Picker (multiple users)",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Approvers",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10004",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Impact",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Select List (single choice)",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Impact",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10005",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Change type",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Select List (single choice)",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Change type",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10006",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Change risk",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Select List (single choice)",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Change risk",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10007",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Change reason",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Choose the reason for the change request",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Select List (single choice)",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Change reason",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10008",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Actual start",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Enter when the change actually started.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Date Time Picker",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Actual start",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10009",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Actual end",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Enter when the change actually ended.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Date Time Picker",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Actual end",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10010",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Request Type",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Holds information about which Service Desk was used to create a ticket. This custom field is created programmatically and must not be modified.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Customer Request Type Custom Field",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Request Type",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10011",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Epic Name",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Provide a short name to identify this epic.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Name of Epic",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Epic Name",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10012",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Epic Status",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Epic Status field for Jira Software use only.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Status of Epic",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Epic Status",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10013",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Epic Color",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Epic Color field for Jira Software use only.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Color of Epic",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Epic Color",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10014",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Epic Link",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Choose an epic to assign this issue to.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Epic Link Relationship",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Epic Link",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10015",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Start date",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Allows the planned start date for a piece of work to be set.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Date Picker",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Start date",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10016",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Story point estimate",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Measurement of complexity and/or size of a requirement.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Number Field",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Story point estimate",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10017",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Issue color",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Issue color",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Issue color",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10018",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Parent Link",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Parent Link",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Parent Link",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field updated",
- "FullyQualifiedName": "AuditRecord/10019",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Parent Link",
- "Summary": "Custom field updated",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field updated",
- "FullyQualifiedName": "AuditRecord/10020",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Story point estimate",
- "Summary": "Custom field updated",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10021",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Rank",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Global rank field for Jira Software use only.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Global Rank",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Rank",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10022",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Sprint",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Jira Software sprint field",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Jira Sprint Field",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Sprint",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10023",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Flagged",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Allows to flag issues with impediments.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Checkboxes",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Flagged",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field context created",
- "FullyQualifiedName": "AuditRecord/10024",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "custom field context",
- "ChangedField0_Name": "Associated to project(s)",
- "ChangedField0_To": "All projects",
- "ChangedField1_Name": "Associated to issue type(s)",
- "ChangedField1_To": "All issue types",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "customfield_10022",
- "Summary": "Field context created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10025",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Target start",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "The targeted start date. This custom field is created and required by Advanced Roadmaps for Jira.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Target start",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Target start",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field context created",
- "FullyQualifiedName": "AuditRecord/10026",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "custom field context",
- "ChangedField0_Name": "Associated to project(s)",
- "ChangedField0_To": "All projects",
- "ChangedField1_Name": "Associated to issue type(s)",
- "ChangedField1_To": "All issue types",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "customfield_10023",
- "Summary": "Field context created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10027",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Target end",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "The targeted end date. This custom field is created and required by Advanced Roadmaps for Jira.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Target end",
- "Created": "2025-05-05T10:25:48.747+0000",
- "Object": "Target end",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Global permission added",
- "FullyQualifiedName": "AuditRecord/10028",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "permissions",
- "ChangedField0_Name": "Permission",
- "ChangedField0_To": "Administer Jira",
- "ChangedField1_Name": "Group name",
- "ChangedField1_To": "org-admins",
- "ChangedField2_Name": "Group",
- "ChangedField2_To": "f94760f8-4a5e-49da-ac1e-0d4281e86aa1",
- "Created": "2025-05-20T09:11:40.435+0000",
- "Object": "Global Permissions",
- "Summary": "Global permission added",
- "Type": "PERMISSIONS"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Global permission added",
- "FullyQualifiedName": "AuditRecord/10029",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "permissions",
- "ChangedField0_Name": "Permission",
- "ChangedField0_To": "Administer Jira",
- "ChangedField1_Name": "Group name",
- "ChangedField1_To": "jira-admins-shaider",
- "ChangedField2_Name": "Group",
- "ChangedField2_To": "e06e77c1-dea1-42ba-a119-ed1189826165",
- "Created": "2025-05-20T09:11:40.465+0000",
- "Object": "Global Permissions",
- "Summary": "Global permission added",
- "Type": "PERMISSIONS"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field context created",
- "FullyQualifiedName": "AuditRecord/10030",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "custom field context",
- "ChangedField0_Name": "Associated to project(s)",
- "ChangedField0_To": "All projects",
- "ChangedField1_Name": "Associated to issue type(s)",
- "ChangedField1_To": "All issue types",
- "Created": "2025-05-20T09:11:41.700+0000",
- "Object": "customfield_10026",
- "Summary": "Field context created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10031",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "[CHART] Date of First Response",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Date of First Response",
- "Created": "2025-05-20T09:11:41.715+0000",
- "Object": "[CHART] Date of First Response",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field context created",
- "FullyQualifiedName": "AuditRecord/10032",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "custom field context",
- "ChangedField0_Name": "Associated to project(s)",
- "ChangedField0_To": "All projects",
- "ChangedField1_Name": "Associated to issue type(s)",
- "ChangedField1_To": "All issue types",
- "Created": "2025-05-20T09:11:41.812+0000",
- "Object": "customfield_10027",
- "Summary": "Field context created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10033",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "[CHART] Time in Status",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Time in Status",
- "Created": "2025-05-20T09:11:41.830+0000",
- "Object": "[CHART] Time in Status",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field context created",
- "FullyQualifiedName": "AuditRecord/10034",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "custom field context",
- "ChangedField0_Name": "Associated to project(s)",
- "ChangedField0_To": "All projects",
- "ChangedField1_Name": "Associated to issue type(s)",
- "ChangedField1_To": "All issue types",
- "Created": "2025-05-20T09:11:43.498+0000",
- "Object": "customfield_10029",
- "Summary": "Field context created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field context created",
- "FullyQualifiedName": "AuditRecord/10035",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "custom field context",
- "ChangedField0_Name": "Associated to project(s)",
- "ChangedField0_To": "All projects",
- "ChangedField1_Name": "Associated to issue type(s)",
- "ChangedField1_To": "All issue types",
- "Created": "2025-05-20T09:11:43.499+0000",
- "Object": "customfield_10031",
- "Summary": "Field context created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field context created",
- "FullyQualifiedName": "AuditRecord/10036",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "custom field context",
- "ChangedField0_Name": "Associated to project(s)",
- "ChangedField0_To": "All projects",
- "ChangedField1_Name": "Associated to issue type(s)",
- "ChangedField1_To": "All issue types",
- "Created": "2025-05-20T09:11:43.499+0000",
- "Object": "customfield_10028",
- "Summary": "Field context created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field context created",
- "FullyQualifiedName": "AuditRecord/10037",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "custom field context",
- "ChangedField0_Name": "Associated to project(s)",
- "ChangedField0_To": "All projects",
- "ChangedField1_Name": "Associated to issue type(s)",
- "ChangedField1_To": "All issue types",
- "Created": "2025-05-20T09:11:43.499+0000",
- "Object": "customfield_10030",
- "Summary": "Field context created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10038",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Open forms",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "The number of open forms on the issue",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Open forms",
- "Created": "2025-05-20T09:11:43.520+0000",
- "Object": "Open forms",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10039",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Submitted forms",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "The number of submitted forms on the issue",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Submitted forms",
- "Created": "2025-05-20T09:11:43.520+0000",
- "Object": "Submitted forms",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10040",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Total forms",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "The total number of forms on the issue",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Total forms",
- "Created": "2025-05-20T09:11:43.523+0000",
- "Object": "Total forms",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10041",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Locked forms",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "The number of locked forms on the issue",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Locked forms",
- "Created": "2025-05-20T09:11:43.536+0000",
- "Object": "Locked forms",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Issue type created",
- "FullyQualifiedName": "AuditRecord/10042",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "issue types",
- "Created": "2025-05-20T09:12:06.211+0000",
- "Object": "Epic",
- "Summary": "Issue type created",
- "Type": "ISSUE_TYPE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Issue type created",
- "FullyQualifiedName": "AuditRecord/10043",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "issue types",
- "Created": "2025-05-20T09:12:06.332+0000",
- "Object": "Subtask",
- "Summary": "Issue type created",
- "Type": "ISSUE_TYPE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Issue type created",
- "FullyQualifiedName": "AuditRecord/10044",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "issue types",
- "Created": "2025-05-20T09:12:06.400+0000",
- "Object": "Task",
- "Summary": "Issue type created",
- "Type": "ISSUE_TYPE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Issue type created",
- "FullyQualifiedName": "AuditRecord/10045",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "issue types",
- "Created": "2025-05-20T09:12:06.468+0000",
- "Object": "Story",
- "Summary": "Issue type created",
- "Type": "ISSUE_TYPE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10046",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "development",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Includes development summary panel information used in JQL",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Dev Summary Custom Field",
- "Created": "2025-05-20T09:12:09.092+0000",
- "Object": "development",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10047",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Design",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Custom field that stores design information for JQL",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Design",
- "Created": "2025-05-20T09:12:09.180+0000",
- "Object": "Design",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10048",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Vulnerability",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Custom field that stores vulnerability information for JQL",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Vulnerability",
- "Created": "2025-05-20T09:12:09.257+0000",
- "Object": "Vulnerability",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Group created",
- "FullyQualifiedName": "AuditRecord/10049",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "group management",
- "Created": "2025-05-20T09:12:11.379+0000",
- "Object": "jira-users-shaider",
- "Summary": "Group created",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Group created",
- "FullyQualifiedName": "AuditRecord/10050",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "group management",
- "Created": "2025-05-20T09:12:11.397+0000",
- "Object": "org-admins",
- "Summary": "Group created",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Group created",
- "FullyQualifiedName": "AuditRecord/10051",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "group management",
- "Created": "2025-05-20T09:12:11.399+0000",
- "Object": "atlassian-addons-admin",
- "Summary": "Group created",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Group created",
- "FullyQualifiedName": "AuditRecord/10052",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "group management",
- "Created": "2025-05-20T09:12:11.408+0000",
- "Object": "jira-admins-shaider",
- "Summary": "Group created",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Group created",
- "FullyQualifiedName": "AuditRecord/10053",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "group management",
- "Created": "2025-05-20T09:12:11.408+0000",
- "Object": "jira-user-access-admins-shaider",
- "Summary": "Group created",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Group created",
- "FullyQualifiedName": "AuditRecord/10054",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "group management",
- "Created": "2025-05-20T09:12:11.414+0000",
- "Object": "system-administrators",
- "Summary": "Group created",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10055",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "d1e68c57-d711-4b14-a2c2-ef4bd13e60b9",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:12:11.414+0000",
- "Object": "jira-users-shaider",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10056",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "d1e68c57-d711-4b14-a2c2-ef4bd13e60b9",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:12:11.407+0000",
- "Object": "org-admins",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10057",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "d1e68c57-d711-4b14-a2c2-ef4bd13e60b9",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T09:12:11.405+0000",
- "Object": "d1e68c57-d711-4b14-a2c2-ef4bd13e60b9",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Workflow created",
- "FullyQualifiedName": "AuditRecord/10058",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "workflows",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Software workflow for project SCRUM",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "",
- "Created": "2025-05-20T09:12:11.729+0000",
- "Object": "Software workflow for project SCRUM",
- "Summary": "Workflow created",
- "Type": "WORKFLOW"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10059",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Created": "2025-05-20T09:12:14.634+0000",
- "Object": "Administrator",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sprint created",
- "FullyQualifiedName": "AuditRecord/10060",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "sprints",
- "Created": "2025-05-20T09:12:19.817+0000",
- "Object": "SCRUM Sprint 1",
- "Summary": "Sprint created",
- "Type": "SPRINT"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field removed from Screen",
- "FullyQualifiedName": "AuditRecord/10061",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "screens",
- "ChangedField0_Name": "Field Removed",
- "ChangedField0_To": "",
- "Created": "2025-05-20T09:12:20.260+0000",
- "Object": "SCRUM-Story",
- "Summary": "Field removed from Screen",
- "Type": "SCREEN"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field updated in Screen",
- "FullyQualifiedName": "AuditRecord/10062",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "screens",
- "ChangedField0_Name": "Field Updated",
- "ChangedField0_To": "Resolution,Reporter,Summary,Flagged,Labels,Attachment,Restrict to,Rank,Assignee,Story point estimate,Linked Issues,Issue Type,Team,Sprint,Description,development,Parent",
- "Created": "2025-05-20T09:12:20.282+0000",
- "Object": "SCRUM-Story",
- "Summary": "Field updated in Screen",
- "Type": "SCREEN"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field removed from Screen",
- "FullyQualifiedName": "AuditRecord/10063",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "screens",
- "ChangedField0_Name": "Field Removed",
- "ChangedField0_To": "",
- "Created": "2025-05-20T09:12:20.363+0000",
- "Object": "SCRUM-Task",
- "Summary": "Field removed from Screen",
- "Type": "SCREEN"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field updated in Screen",
- "FullyQualifiedName": "AuditRecord/10064",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "screens",
- "ChangedField0_Name": "Field Updated",
- "ChangedField0_To": "Flagged,Issue Type,Labels,Attachment,Team,Rank,Story point estimate,development,Parent,Reporter,Restrict to,Description,Assignee,Summary,Linked Issues,Resolution,Sprint",
- "Created": "2025-05-20T09:12:20.383+0000",
- "Object": "SCRUM-Task",
- "Summary": "Field updated in Screen",
- "Type": "SCREEN"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field removed from Screen",
- "FullyQualifiedName": "AuditRecord/10065",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "screens",
- "ChangedField0_Name": "Field Removed",
- "ChangedField0_To": "",
- "Created": "2025-05-20T09:12:20.464+0000",
- "Object": "SCRUM - Subtask",
- "Summary": "Field removed from Screen",
- "Type": "SCREEN"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field updated in Screen",
- "FullyQualifiedName": "AuditRecord/10066",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "screens",
- "ChangedField0_Name": "Field Updated",
- "ChangedField0_To": "Labels,Attachment,Restrict to,Assignee,Linked Issues,Issue Type,Sprint,development,Rank,Reporter,Summary,Flagged,Story point estimate,Description,Team,Parent,Resolution",
- "Created": "2025-05-20T09:12:20.478+0000",
- "Object": "SCRUM - Subtask",
- "Summary": "Field updated in Screen",
- "Type": "SCREEN"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field configuration scheme updated",
- "FullyQualifiedName": "AuditRecord/10067",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "fields",
- "ChangedField0_Name": "Issue Type",
- "ChangedField0_To": "Task",
- "ChangedField1_Name": "Field Configuration",
- "ChangedField1_To": "LEARNJIRA-10005",
- "Created": "2025-05-20T09:12:28.876+0000",
- "Object": "Field Configuration Scheme for Project LEARNJIRA",
- "Summary": "Field configuration scheme updated",
- "Type": "SCHEME"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Issue type created",
- "FullyQualifiedName": "AuditRecord/10068",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "issue types",
- "Created": "2025-05-20T09:12:29.155+0000",
- "Object": "Task",
- "Summary": "Issue type created",
- "Type": "ISSUE_TYPE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Workflow created",
- "FullyQualifiedName": "AuditRecord/10069",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "workflows",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Software workflow for project 10001",
- "Created": "2025-05-20T09:12:29.792+0000",
- "Object": "Software workflow for project 10001",
- "Summary": "Workflow created",
- "Type": "WORKFLOW"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field configuration scheme updated",
- "FullyQualifiedName": "AuditRecord/10070",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "fields",
- "ChangedField0_Name": "Issue Type",
- "ChangedField0_To": "Epic",
- "ChangedField1_Name": "Field Configuration",
- "ChangedField1_To": "LEARNJIRA-10006",
- "Created": "2025-05-20T09:12:30.456+0000",
- "Object": "Field Configuration Scheme for Project LEARNJIRA",
- "Summary": "Field configuration scheme updated",
- "Type": "SCHEME"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Issue type created",
- "FullyQualifiedName": "AuditRecord/10071",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "issue types",
- "Created": "2025-05-20T09:12:30.746+0000",
- "Object": "Epic",
- "Summary": "Issue type created",
- "Type": "ISSUE_TYPE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field configuration scheme updated",
- "FullyQualifiedName": "AuditRecord/10072",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "fields",
- "ChangedField0_Name": "Issue Type",
- "ChangedField0_To": "Subtask",
- "ChangedField1_Name": "Field Configuration",
- "ChangedField1_To": "LEARNJIRA-10007",
- "Created": "2025-05-20T09:12:31.363+0000",
- "Object": "Field Configuration Scheme for Project LEARNJIRA",
- "Summary": "Field configuration scheme updated",
- "Type": "SCHEME"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Issue type created",
- "FullyQualifiedName": "AuditRecord/10073",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "issue types",
- "Created": "2025-05-20T09:12:31.461+0000",
- "Object": "Subtask",
- "Summary": "Issue type created",
- "Type": "ISSUE_TYPE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project created",
- "FullyQualifiedName": "AuditRecord/10074",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "d1e68c57-d711-4b14-a2c2-ef4bd13e60b9",
- "AssociatedItem0_Type": "USER",
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "projects",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "(Learn) Jira Premium benefits in 5 min 👋",
- "ChangedField1_Name": "Key",
- "ChangedField1_To": "LEARNJIRA",
- "ChangedField2_Name": "Description",
- "ChangedField2_To": "",
- "ChangedField3_Name": "Project lead",
- "ChangedField3_To": "d1e68c57-d711-4b14-a2c2-ef4bd13e60b9",
- "ChangedField4_Name": "Default Assignee",
- "ChangedField4_To": "Unassigned",
- "Created": "2025-05-20T09:12:31.995+0000",
- "Object": "(Learn) Jira Premium benefits in 5 min 👋",
- "Summary": "Project created",
- "Type": "PROJECT"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10075",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Story Points",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Measurement of complexity and/or size of a requirement.",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Number Field",
- "Created": "2025-05-20T09:12:34.016+0000",
- "Object": "Story Points",
- "RemoteAddress": "10.20.110.172",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10076",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "60e5a86a471e61006a4c51fd",
- "Created": "2025-05-20T09:16:46.236+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10077",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "60e5a86a471e61006a4c51fd",
- "Created": "2025-05-20T09:16:46.289+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10078",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "60e5a86a471e61006a4c51fd",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:16:46.636+0000",
- "Object": "jira-users-shaider",
- "RemoteAddress": "10.26.66.143",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10079",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd",
- "Created": "2025-05-20T09:16:49.651+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10080",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd",
- "Created": "2025-05-20T09:16:49.683+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10081",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:16:50.382+0000",
- "Object": "jira-users-shaider",
- "RemoteAddress": "10.26.66.143",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10082",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd",
- "Created": "2025-05-20T09:16:52.052+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10083",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd",
- "Created": "2025-05-20T09:16:52.107+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10084",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "5b6c7b3afbc68529c6c47967",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:16:52.761+0000",
- "Object": "jira-users-shaider",
- "RemoteAddress": "10.26.66.143",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10085",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "Created": "2025-05-20T09:16:55.592+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10086",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "Created": "2025-05-20T09:16:55.622+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10087",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:16:56.370+0000",
- "Object": "jira-admins-shaider",
- "RemoteAddress": "10.16.132.66",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10088",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:16:56.411+0000",
- "Object": "jira-users-shaider",
- "RemoteAddress": "10.16.132.66",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10089",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077, 5d53f3cbc6b9320d9ea5bdc2",
- "Created": "2025-05-20T09:16:57.962+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10090",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 5d53f3cbc6b9320d9ea5bdc2, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "Created": "2025-05-20T09:16:57.988+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10091",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "5d53f3cbc6b9320d9ea5bdc2",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:16:58.595+0000",
- "Object": "jira-users-shaider",
- "RemoteAddress": "10.26.105.37",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10092",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd, 5cb4ae0e4b97ab11a18e00c7, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077, 5d53f3cbc6b9320d9ea5bdc2",
- "Created": "2025-05-20T09:17:00.720+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10093",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "5cb4ae0e4b97ab11a18e00c7, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 5d53f3cbc6b9320d9ea5bdc2, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "Created": "2025-05-20T09:17:00.751+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10094",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "5cb4ae0e4b97ab11a18e00c7",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:01.225+0000",
- "Object": "jira-admins-shaider",
- "RemoteAddress": "10.16.132.66",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10095",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "5cb4ae0e4b97ab11a18e00c7",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:01.927+0000",
- "Object": "jira-users-shaider",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10096",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5cb4ae0e4b97ab11a18e00c7, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077, 5d53f3cbc6b9320d9ea5bdc2, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e",
- "Created": "2025-05-20T09:17:03.279+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10097",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "5cb4ae0e4b97ab11a18e00c7, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 5d53f3cbc6b9320d9ea5bdc2, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "Created": "2025-05-20T09:17:03.306+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10098",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "557058:0867a421-a9ee-4659-801a-bc0ee4a4487e",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:03.773+0000",
- "Object": "jira-users-shaider",
- "RemoteAddress": "10.26.105.37",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10099",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5cb4ae0e4b97ab11a18e00c7, 60e5a86a471e61006a4c51fd, 557058:950f9f5b-3d6d-4e1d-954a-21367ae9ac75, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077, 5d53f3cbc6b9320d9ea5bdc2, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e",
- "Created": "2025-05-20T09:17:06.320+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10100",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "5cb4ae0e4b97ab11a18e00c7, 557058:950f9f5b-3d6d-4e1d-954a-21367ae9ac75, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 5d53f3cbc6b9320d9ea5bdc2, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "Created": "2025-05-20T09:17:06.357+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10101",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "a71b105a-ea02-4b84-a162-64b8abccda81",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:06.383+0000",
- "Object": "atlassian-addons-admin",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10102",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "a71b105a-ea02-4b84-a162-64b8abccda81",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T09:17:06.386+0000",
- "Object": "a71b105a-ea02-4b84-a162-64b8abccda81",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10103",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "a71b105a-ea02-4b84-a162-64b8abccda81",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:06.821+0000",
- "Object": "jira-users-shaider",
- "RemoteAddress": "10.26.72.216",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10104",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "a71b105a-ea02-4b84-a162-64b8abccda81",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:07.034+0000",
- "Object": "jira-admins-shaider",
- "RemoteAddress": "10.16.132.66",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10105",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5cb4ae0e4b97ab11a18e00c7, 557058:950f9f5b-3d6d-4e1d-954a-21367ae9ac75, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077, 5d53f3cbc6b9320d9ea5bdc2, 5dd64082af96bc0efbe55103, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e",
- "Created": "2025-05-20T09:17:08.228+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10106",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "5cb4ae0e4b97ab11a18e00c7, 557058:950f9f5b-3d6d-4e1d-954a-21367ae9ac75, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 5d53f3cbc6b9320d9ea5bdc2, 5dd64082af96bc0efbe55103, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "Created": "2025-05-20T09:17:08.256+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10107",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "c6a62781-e4f9-484c-baa8-0ee189f25039",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T09:17:08.450+0000",
- "Object": "c6a62781-e4f9-484c-baa8-0ee189f25039",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10108",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "c6a62781-e4f9-484c-baa8-0ee189f25039",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:09.139+0000",
- "Object": "jira-users-shaider",
- "RemoteAddress": "10.26.66.143",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10109",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T09:17:11.866+0000",
- "Object": "e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7",
- "RemoteAddress": "10.26.72.216",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10110",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "63a22fb348b367d78a14c15b, 5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5cb4ae0e4b97ab11a18e00c7, 557058:950f9f5b-3d6d-4e1d-954a-21367ae9ac75, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077, 5d53f3cbc6b9320d9ea5bdc2, 5dd64082af96bc0efbe55103, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e",
- "Created": "2025-05-20T09:17:11.960+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10111",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "63a22fb348b367d78a14c15b, 5cb4ae0e4b97ab11a18e00c7, 557058:950f9f5b-3d6d-4e1d-954a-21367ae9ac75, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 5d53f3cbc6b9320d9ea5bdc2, 5dd64082af96bc0efbe55103, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "Created": "2025-05-20T09:17:11.992+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User removed from group",
- "FullyQualifiedName": "AuditRecord/10112",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:12.169+0000",
- "Object": "atlassian-addons-admin",
- "Summary": "User removed from group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10113",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T09:17:12.191+0000",
- "Object": "e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10114",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:12.446+0000",
- "Object": "atlassian-addons-admin",
- "RemoteAddress": "10.16.132.66",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10115",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:12.511+0000",
- "Object": "jira-admins-shaider",
- "RemoteAddress": "10.16.132.66",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10116",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:12.644+0000",
- "Object": "jira-users-shaider",
- "RemoteAddress": "10.26.66.143",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10117",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "63a22fb348b367d78a14c15b, 5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5cb4ae0e4b97ab11a18e00c7, 557058:950f9f5b-3d6d-4e1d-954a-21367ae9ac75, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077, 5d53f3cbc6b9320d9ea5bdc2, 5dd64082af96bc0efbe55103, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e, 5cf112d31552030f1e3a5905",
- "Created": "2025-05-20T09:17:14.744+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10118",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "63a22fb348b367d78a14c15b, 5cb4ae0e4b97ab11a18e00c7, 557058:950f9f5b-3d6d-4e1d-954a-21367ae9ac75, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 5cf112d31552030f1e3a5905, 5d53f3cbc6b9320d9ea5bdc2, 5dd64082af96bc0efbe55103, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "Created": "2025-05-20T09:17:14.775+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10119",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "5cf112d31552030f1e3a5905",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:15.565+0000",
- "Object": "jira-users-shaider",
- "RemoteAddress": "10.16.149.189",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10120",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "63a22fb348b367d78a14c15b, 5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5cb4ae0e4b97ab11a18e00c7, 557058:950f9f5b-3d6d-4e1d-954a-21367ae9ac75, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077, 5d53f3cbc6b9320d9ea5bdc2, 5dd64082af96bc0efbe55103, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e, 5cf112d31552030f1e3a5905, 630db2cd9796033b256bc349",
- "Created": "2025-05-20T09:17:18.574+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Project roles changed",
- "FullyQualifiedName": "AuditRecord/10121",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "My Scrum Project",
- "AssociatedItem0_Type": "PROJECT",
- "Category": "projects",
- "ChangedField0_Name": "Users",
- "ChangedField0_To": "63a22fb348b367d78a14c15b, 5cb4ae0e4b97ab11a18e00c7, 557058:950f9f5b-3d6d-4e1d-954a-21367ae9ac75, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e, 630db2cd9796033b256bc349, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 5cf112d31552030f1e3a5905, 5d53f3cbc6b9320d9ea5bdc2, 5dd64082af96bc0efbe55103, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077",
- "Created": "2025-05-20T09:17:18.605+0000",
- "Object": "atlassian-addons-project-access",
- "Summary": "Project roles changed",
- "Type": "PROJECT_ROLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10122",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "630db2cd9796033b256bc349",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:19.009+0000",
- "Object": "jira-admins-shaider",
- "RemoteAddress": "10.26.66.143",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10123",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "630db2cd9796033b256bc349",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-20T09:17:19.052+0000",
- "Object": "jira-users-shaider",
- "RemoteAddress": "10.16.132.66",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field context created",
- "FullyQualifiedName": "AuditRecord/10124",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "custom field context",
- "ChangedField0_Name": "Associated to project(s)",
- "ChangedField0_To": "All projects",
- "ChangedField1_Name": "Associated to issue type(s)",
- "ChangedField1_To": "All issue types",
- "Created": "2025-05-20T09:17:19.186+0000",
- "Object": "com.atlassian.atlas.jira__project-key",
- "Summary": "Field context created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10125",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Project overview key",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Key of project overview connected via Atlassian Home for Jira Cloud",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Project overview key",
- "Created": "2025-05-20T09:17:19.206+0000",
- "Object": "Project overview key",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Field context created",
- "FullyQualifiedName": "AuditRecord/10126",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "custom field context",
- "ChangedField0_Name": "Associated to project(s)",
- "ChangedField0_To": "All projects",
- "ChangedField1_Name": "Associated to issue type(s)",
- "ChangedField1_To": "All issue types",
- "Created": "2025-05-20T09:17:19.372+0000",
- "Object": "com.atlassian.atlas.jira__project-status",
- "Summary": "Field context created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Custom field created",
- "FullyQualifiedName": "AuditRecord/10127",
- "Type": "AuditRecord",
- "Metadata": {
- "Category": "fields",
- "ChangedField0_Name": "Name",
- "ChangedField0_To": "Project overview status",
- "ChangedField1_Name": "Description",
- "ChangedField1_To": "Status of project overview connected via Atlassian Home for Jira Cloud",
- "ChangedField2_Name": "Type",
- "ChangedField2_To": "Project overview status",
- "Created": "2025-05-20T09:17:19.389+0000",
- "Object": "Project overview status",
- "Summary": "Custom field created",
- "Type": "CUSTOM_FIELD"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10160",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "cbefd655-3c82-41d7-993b-1e39524f1a70",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:16.406+0000",
- "Object": "cbefd655-3c82-41d7-993b-1e39524f1a70",
- "RemoteAddress": "10.26.109.230",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10161",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "127e0d88-4090-4621-9ba6-e821b3337030",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:18.580+0000",
- "Object": "127e0d88-4090-4621-9ba6-e821b3337030",
- "RemoteAddress": "10.26.69.20",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10162",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "fd5c50d8-5ffe-45a5-8f0f-710d02cfd080",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:19.363+0000",
- "Object": "fd5c50d8-5ffe-45a5-8f0f-710d02cfd080",
- "RemoteAddress": "10.26.124.174",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10163",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "c0183bc4-5673-42a3-a769-1c91715c92c6",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:20.260+0000",
- "Object": "c0183bc4-5673-42a3-a769-1c91715c92c6",
- "RemoteAddress": "10.26.124.174",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10164",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "08251f11-274a-43e5-8672-dd60e972654a",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:21.481+0000",
- "Object": "08251f11-274a-43e5-8672-dd60e972654a",
- "RemoteAddress": "10.16.140.214",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10165",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "1c0e598f-5a5e-49bf-b0aa-6251ac808027",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:22.698+0000",
- "Object": "1c0e598f-5a5e-49bf-b0aa-6251ac808027",
- "RemoteAddress": "10.26.124.174",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10166",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "72f57329-34af-40e2-a217-40128d049fa9",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:24.162+0000",
- "Object": "72f57329-34af-40e2-a217-40128d049fa9",
- "RemoteAddress": "10.26.72.252",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10167",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "b73e79da-3732-42dc-98f8-5cfe2765fbcf",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:25.105+0000",
- "Object": "b73e79da-3732-42dc-98f8-5cfe2765fbcf",
- "RemoteAddress": "10.26.72.223",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10168",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "dd62e8db-f985-461c-8957-630cba36dca3",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:25.787+0000",
- "Object": "dd62e8db-f985-461c-8957-630cba36dca3",
- "RemoteAddress": "10.26.69.20",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10169",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "95850660-2865-498a-a78c-ea40add0f257",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:27.277+0000",
- "Object": "95850660-2865-498a-a78c-ea40add0f257",
- "RemoteAddress": "10.26.124.174",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10170",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "2b683cd5-f6b2-4d41-8383-6987df3c5337",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:28.110+0000",
- "Object": "2b683cd5-f6b2-4d41-8383-6987df3c5337",
- "RemoteAddress": "10.26.109.230",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10171",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "55e4b181-4a42-45ed-bcc3-0c0576b8a709",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:29.603+0000",
- "Object": "55e4b181-4a42-45ed-bcc3-0c0576b8a709",
- "RemoteAddress": "10.26.72.252",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10172",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "ec58c96c-2129-4781-81d9-199189807ea5",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:31.305+0000",
- "Object": "ec58c96c-2129-4781-81d9-199189807ea5",
- "RemoteAddress": "10.16.130.180",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10173",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "ef289a7b-3601-4d0d-903c-eee4a53695b5",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:31.429+0000",
- "Object": "ef289a7b-3601-4d0d-903c-eee4a53695b5",
- "RemoteAddress": "10.26.69.20",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10174",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "adbe0dd1-5afb-44cb-a662-16151eaa2ee2",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:32.418+0000",
- "Object": "adbe0dd1-5afb-44cb-a662-16151eaa2ee2",
- "RemoteAddress": "10.26.72.252",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10175",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "491b809f-064b-48a2-9d98-8940cce0fa81",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:34.218+0000",
- "Object": "491b809f-064b-48a2-9d98-8940cce0fa81",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10176",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "0b0ad180-899d-4f27-a526-ce558ee2b454",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:34.807+0000",
- "Object": "0b0ad180-899d-4f27-a526-ce558ee2b454",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10177",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "ef5f609c-8acd-4cdd-a867-caea2cb04201",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:36.413+0000",
- "Object": "ef5f609c-8acd-4cdd-a867-caea2cb04201",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10178",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "a47aa1f1-5663-48cc-ab44-50a0a18f7efd",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:37.039+0000",
- "Object": "a47aa1f1-5663-48cc-ab44-50a0a18f7efd",
- "RemoteAddress": "10.26.72.252",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10179",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "3696b761-cc6f-481e-9a33-08b3367b7bce",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:38.456+0000",
- "Object": "3696b761-cc6f-481e-9a33-08b3367b7bce",
- "RemoteAddress": "10.16.142.95",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10180",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "15439766-f128-4fcb-b55a-bce2d3179d6c",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:40.015+0000",
- "Object": "15439766-f128-4fcb-b55a-bce2d3179d6c",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10181",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "d4576f35-c3fc-466e-afaa-24dca7167c91",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:40.500+0000",
- "Object": "d4576f35-c3fc-466e-afaa-24dca7167c91",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10182",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "73a7eb7c-322b-449c-9a9b-4eca296ac805",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:41.451+0000",
- "Object": "73a7eb7c-322b-449c-9a9b-4eca296ac805",
- "RemoteAddress": "10.16.140.214",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10183",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "bd41f0b5-f733-47f1-bae7-ab71a923f129",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:42.752+0000",
- "Object": "bd41f0b5-f733-47f1-bae7-ab71a923f129",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10184",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "6fbf8958-93f4-4cb4-9bcc-8f3756bcd2c2",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:44.141+0000",
- "Object": "6fbf8958-93f4-4cb4-9bcc-8f3756bcd2c2",
- "RemoteAddress": "10.26.124.174",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10185",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "06425cbc-036a-4da4-bcb9-659f0e1478cf",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:45.192+0000",
- "Object": "06425cbc-036a-4da4-bcb9-659f0e1478cf",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10186",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "6fa0ac8d-61b5-4954-b81f-8d32a9c3ac30",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:45.976+0000",
- "Object": "6fa0ac8d-61b5-4954-b81f-8d32a9c3ac30",
- "RemoteAddress": "10.26.69.20",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10187",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "3d59bbf5-af46-4cc4-8dd5-7869dd05a0bd",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:47.373+0000",
- "Object": "3d59bbf5-af46-4cc4-8dd5-7869dd05a0bd",
- "RemoteAddress": "10.26.113.166",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10188",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "e842d31a-bc10-4448-af5f-db6bf6999f7e",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:48.431+0000",
- "Object": "e842d31a-bc10-4448-af5f-db6bf6999f7e",
- "RemoteAddress": "10.26.124.174",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10189",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "05c895af-5335-46ca-9c2d-2a73e2b360a8",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:49.654+0000",
- "Object": "05c895af-5335-46ca-9c2d-2a73e2b360a8",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10190",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "bbba649f-2d48-4661-82c0-dfa3393a3215",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:51.717+0000",
- "Object": "bbba649f-2d48-4661-82c0-dfa3393a3215",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10191",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "a21b45de-4a21-4c77-a920-6cb658e0d2d5",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:51.735+0000",
- "Object": "a21b45de-4a21-4c77-a920-6cb658e0d2d5",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10192",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "f956eab6-78f6-4bde-918d-23273d2674ec",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:52.550+0000",
- "Object": "f956eab6-78f6-4bde-918d-23273d2674ec",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10193",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "f1fb816a-9862-4424-80bc-c6d8503efd96",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:53.870+0000",
- "Object": "f1fb816a-9862-4424-80bc-c6d8503efd96",
- "RemoteAddress": "10.26.69.20",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10194",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "5788ffcd-e20c-43e5-b4e1-af981de34331",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:55.333+0000",
- "Object": "5788ffcd-e20c-43e5-b4e1-af981de34331",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10195",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "b25c7d8e-dee0-4211-8d81-554a49bf29e6",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:56.566+0000",
- "Object": "b25c7d8e-dee0-4211-8d81-554a49bf29e6",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10196",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "d6257f38-b615-47ac-ba65-a87cf6a2b862",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:57.923+0000",
- "Object": "d6257f38-b615-47ac-ba65-a87cf6a2b862",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10197",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "d651c2e1-ff94-4f17-9238-0b428a652875",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:56:58.830+0000",
- "Object": "d651c2e1-ff94-4f17-9238-0b428a652875",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10198",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "cb3435fb-aa28-47bf-b4ec-74639485ccd1",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-20T19:57:00.662+0000",
- "Object": "cb3435fb-aa28-47bf-b4ec-74639485ccd1",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Issue type scheme updated",
- "FullyQualifiedName": "AuditRecord/10226",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "issue type scheme",
- "Created": "2025-05-26T10:19:43.125+0000",
- "Object": "Default Issue Type Scheme",
- "RemoteAddress": "10.20.41.84",
- "Summary": "Issue type scheme updated",
- "Type": "ISSUE_TYPE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Issue type created",
- "FullyQualifiedName": "AuditRecord/10227",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "issue types",
- "Created": "2025-05-26T10:19:43.153+0000",
- "Object": "Story",
- "RemoteAddress": "10.20.41.84",
- "Summary": "Issue type created",
- "Type": "ISSUE_TYPE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sprint created",
- "FullyQualifiedName": "AuditRecord/10228",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "sprints",
- "Created": "2025-05-26T10:41:38.965+0000",
- "Object": "SCRUM Sprint 2",
- "RemoteAddress": "10.20.108.78",
- "Summary": "Sprint created",
- "Type": "SPRINT"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sprint deleted",
- "FullyQualifiedName": "AuditRecord/10229",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "sprints",
- "Created": "2025-05-26T10:42:18.259+0000",
- "Object": "SCRUM Sprint 2",
- "RemoteAddress": "10.20.41.84",
- "Summary": "Sprint deleted",
- "Type": "SPRINT"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sprint started",
- "FullyQualifiedName": "AuditRecord/10230",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "sprints",
- "Created": "2025-05-26T10:44:01.559+0000",
- "Object": "SCRUM Sprint 1",
- "RemoteAddress": "10.22.111.74",
- "Summary": "Sprint started",
- "Type": "SPRINT"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sprint updated",
- "FullyQualifiedName": "AuditRecord/10231",
- "Type": "AuditRecord",
- "Metadata": {
- "AuthorAccountID": "712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Category": "sprints",
- "Created": "2025-05-26T10:44:01.564+0000",
- "Object": "SCRUM Sprint 1",
- "RemoteAddress": "10.22.111.74",
- "Summary": "Sprint updated",
- "Type": "SPRINT"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User created",
- "FullyQualifiedName": "AuditRecord/10259",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "cf5b456d-6059-48dc-995a-04e18681dc21",
- "AssociatedItem0_Type": "USER",
- "Category": "user management",
- "ChangedField0_Name": "Active / Inactive",
- "ChangedField0_To": "Active",
- "Created": "2025-05-29T11:12:19.464+0000",
- "Object": "cf5b456d-6059-48dc-995a-04e18681dc21",
- "Summary": "User created",
- "Type": "USER"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "User added to group",
- "FullyQualifiedName": "AuditRecord/10260",
- "Type": "AuditRecord",
- "Metadata": {
- "AssociatedItem0_Name": "cf5b456d-6059-48dc-995a-04e18681dc21",
- "AssociatedItem0_Type": "USER",
- "Category": "group management",
- "Created": "2025-05-29T11:12:19.567+0000",
- "Object": "jira-users-shaider",
- "Summary": "User added to group",
- "Type": "GROUP"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM board",
- "FullyQualifiedName": "Board/1",
- "Type": "Board",
- "Metadata": {
- "AvatarURI": "https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10425?size=small",
- "BoardSelfURL": "https://shaider.atlassian.net/rest/agile/1.0/board/1",
- "BoardType": "simple",
- "DisplayName": "My Scrum Project (SCRUM)",
- "IsPrivate": "false",
- "ProjectID": "10000",
- "ProjectKey": "SCRUM",
- "ProjectName": "My Scrum Project",
- "ProjectType": "software"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM board",
- "FullyQualifiedName": "Board/1",
- "Type": "Board",
- "Metadata": {
- "AvatarURI": "https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10425?size=small",
- "BoardSelfURL": "https://shaider.atlassian.net/rest/agile/1.0/board/1",
- "BoardType": "simple",
- "DisplayName": "My Scrum Project (SCRUM)",
- "IsPrivate": "false",
- "ProjectID": "10000",
- "ProjectKey": "SCRUM",
- "ProjectName": "My Scrum Project",
- "ProjectType": "software"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "browse_projects",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM board",
- "FullyQualifiedName": "Board/1",
- "Type": "Board",
- "Metadata": {
- "AvatarURI": "https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10425?size=small",
- "BoardSelfURL": "https://shaider.atlassian.net/rest/agile/1.0/board/1",
- "BoardType": "simple",
- "DisplayName": "My Scrum Project (SCRUM)",
- "IsPrivate": "false",
- "ProjectID": "10000",
- "ProjectKey": "SCRUM",
- "ProjectName": "My Scrum Project",
- "ProjectType": "software"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_sprints_permission",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "(Learn) Jira Premium benefits in 5 min",
- "FullyQualifiedName": "Board/2",
- "Type": "Board",
- "Metadata": {
- "AvatarURI": "https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10411?size=small",
- "BoardSelfURL": "https://shaider.atlassian.net/rest/agile/1.0/board/2",
- "BoardType": "simple",
- "DisplayName": "(Learn) Jira Premium benefits in 5 min 👋 (LEARNJIRA)",
- "IsPrivate": "false",
- "ProjectID": "10001",
- "ProjectKey": "LEARNJIRA",
- "ProjectName": "(Learn) Jira Premium benefits in 5 min 👋",
- "ProjectType": "software"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "(Learn) Jira Premium benefits in 5 min",
- "FullyQualifiedName": "Board/2",
- "Type": "Board",
- "Metadata": {
- "AvatarURI": "https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10411?size=small",
- "BoardSelfURL": "https://shaider.atlassian.net/rest/agile/1.0/board/2",
- "BoardType": "simple",
- "DisplayName": "(Learn) Jira Premium benefits in 5 min 👋 (LEARNJIRA)",
- "IsPrivate": "false",
- "ProjectID": "10001",
- "ProjectKey": "LEARNJIRA",
- "ProjectName": "(Learn) Jira Premium benefits in 5 min 👋",
- "ProjectType": "software"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "browse_projects",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "(Learn) Jira Premium benefits in 5 min",
- "FullyQualifiedName": "Board/2",
- "Type": "Board",
- "Metadata": {
- "AvatarURI": "https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10411?size=small",
- "BoardSelfURL": "https://shaider.atlassian.net/rest/agile/1.0/board/2",
- "BoardType": "simple",
- "DisplayName": "(Learn) Jira Premium benefits in 5 min 👋 (LEARNJIRA)",
- "IsPrivate": "false",
- "ProjectID": "10001",
- "ProjectKey": "LEARNJIRA",
- "ProjectName": "(Learn) Jira Premium benefits in 5 min 👋",
- "ProjectType": "software"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_sprints_permission",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "add_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assign_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "close_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "link_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "modify_reporter",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "move_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "resolve_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "schedule_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "set_issue_security",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "transition_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "unarchive_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_voters_and_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-2",
- "FullyQualifiedName": "Epic/10034",
- "Type": "Epic",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Notification Service"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "work_on_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "system-administrators",
- "FullyQualifiedName": "Group/183f64ca-3ef5-469a-82cb-a84da8b11cbd",
- "Type": "Group",
- "Metadata": {
- "HTML": "system-administrators"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "jira-users-shaider",
- "FullyQualifiedName": "Group/6b44ab2c-c950-4bd7-8e51-a0e35ea95bce",
- "Type": "Group",
- "Metadata": {
- "HTML": "jira-users-shaider",
- "Label0_Text": "Jira Software",
- "Label0_Title": "Users added to this group will be given access to \u003cstrong\u003eJira Software\u003c/strong\u003e",
- "Label0_Type": "SINGLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "jira-user-access-admins-shaider",
- "FullyQualifiedName": "Group/7c9b5ee2-fe05-4b6e-9c88-d6d4887caf7c",
- "Type": "Group",
- "Metadata": {
- "HTML": "jira-user-access-admins-shaider"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "jira-admins-shaider",
- "FullyQualifiedName": "Group/e06e77c1-dea1-42ba-a119-ed1189826165",
- "Type": "Group",
- "Metadata": {
- "HTML": "jira-admins-shaider",
- "Label0_Text": "Admin",
- "Label0_Title": "Users added to this group will be given administrative access",
- "Label0_Type": "ADMIN"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "atlassian-addons-admin",
- "FullyQualifiedName": "Group/f04dd022-c413-4790-aed4-0c7b5167ec31",
- "Type": "Group",
- "Metadata": {
- "HTML": "atlassian-addons-admin"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "org-admins",
- "FullyQualifiedName": "Group/f94760f8-4a5e-49da-ac1e-0d4281e86aa1",
- "Type": "Group",
- "Metadata": {
- "HTML": "org-admins",
- "Label0_Text": "Admin",
- "Label0_Title": "Users added to this group will be given administrative access",
- "Label0_Type": "ADMIN",
- "Label1_Text": "Jira Software",
- "Label1_Title": "Users added to this group will be given access to \u003cstrong\u003eJira Software\u003c/strong\u003e",
- "Label1_Type": "SINGLE"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My Scrum Project",
- "FullyQualifiedName": "Project/10000",
- "Type": "Project",
- "Metadata": {
- "Key": "SCRUM",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "6fc39a42-a49a-4ba6-8fe0-d0e23787eb39"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My Scrum Project",
- "FullyQualifiedName": "Project/10000",
- "Type": "Project",
- "Metadata": {
- "Key": "SCRUM",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "6fc39a42-a49a-4ba6-8fe0-d0e23787eb39"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer_projects",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My Scrum Project",
- "FullyQualifiedName": "Project/10000",
- "Type": "Project",
- "Metadata": {
- "Key": "SCRUM",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "6fc39a42-a49a-4ba6-8fe0-d0e23787eb39"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "browse_projects",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My Scrum Project",
- "FullyQualifiedName": "Project/10000",
- "Type": "Project",
- "Metadata": {
- "Key": "SCRUM",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "6fc39a42-a49a-4ba6-8fe0-d0e23787eb39"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_project",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My Scrum Project",
- "FullyQualifiedName": "Project/10000",
- "Type": "Project",
- "Metadata": {
- "Key": "SCRUM",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "6fc39a42-a49a-4ba6-8fe0-d0e23787eb39"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issue_layout",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My Scrum Project",
- "FullyQualifiedName": "Project/10000",
- "Type": "Project",
- "Metadata": {
- "Key": "SCRUM",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "6fc39a42-a49a-4ba6-8fe0-d0e23787eb39"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_dev_tools",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "FullyQualifiedName": "Project/10001",
- "Type": "Project",
- "Metadata": {
- "Key": "LEARNJIRA",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "05b93f07-2de4-4a25-82ce-0ec40b666b3b"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "FullyQualifiedName": "Project/10001",
- "Type": "Project",
- "Metadata": {
- "Key": "LEARNJIRA",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "05b93f07-2de4-4a25-82ce-0ec40b666b3b"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer_projects",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "FullyQualifiedName": "Project/10001",
- "Type": "Project",
- "Metadata": {
- "Key": "LEARNJIRA",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "05b93f07-2de4-4a25-82ce-0ec40b666b3b"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "browse_projects",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "FullyQualifiedName": "Project/10001",
- "Type": "Project",
- "Metadata": {
- "Key": "LEARNJIRA",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "05b93f07-2de4-4a25-82ce-0ec40b666b3b"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_project",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "FullyQualifiedName": "Project/10001",
- "Type": "Project",
- "Metadata": {
- "Key": "LEARNJIRA",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "05b93f07-2de4-4a25-82ce-0ec40b666b3b"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issue_layout",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "(Learn) Jira Premium benefits in 5 min 👋",
- "FullyQualifiedName": "Project/10001",
- "Type": "Project",
- "Metadata": {
- "Key": "LEARNJIRA",
- "Private": "false",
- "TypeKey": "software",
- "UUID": "05b93f07-2de4-4a25-82ce-0ec40b666b3b"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_dev_tools",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "add_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assign_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "close_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "link_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "modify_reporter",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "move_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "resolve_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "schedule_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "set_issue_security",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "transition_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "unarchive_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_voters_and_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-6",
- "FullyQualifiedName": "Story/10038",
- "Type": "Story",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Story"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "work_on_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "add_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assign_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "close_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "link_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "modify_reporter",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "move_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "resolve_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "schedule_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "set_issue_security",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "transition_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "unarchive_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_voters_and_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-5",
- "FullyQualifiedName": "Subtask/10037",
- "Type": "Subtask",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "In Progress",
- "Summary": "first sub-task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "work_on_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "add_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assign_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "close_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "link_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "modify_reporter",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "move_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "resolve_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "schedule_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "set_issue_security",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "transition_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "unarchive_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_voters_and_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-1",
- "FullyQualifiedName": "Task/10000",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Security \u0026 permissions: How to control who can edit or or manage projects 🚧"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "work_on_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "add_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assign_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "close_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "link_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "modify_reporter",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "move_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "resolve_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "schedule_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "set_issue_security",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "transition_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "unarchive_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_voters_and_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-2",
- "FullyQualifiedName": "Task/10001",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Plans: How to use detailed roadmaps to plan out your work 📍"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "work_on_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "add_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assign_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "close_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "link_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "modify_reporter",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "move_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "resolve_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "schedule_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "set_issue_security",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "transition_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "unarchive_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_voters_and_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "LEARNJIRA-3",
- "FullyQualifiedName": "Task/10002",
- "Type": "Task",
- "Metadata": {
- "Project": "LEARNJIRA",
- "Status": "To Do",
- "Summary": "Atlassian Intelligence: How to work smarter with AI 🤖"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "work_on_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "add_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assign_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "close_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "link_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "modify_reporter",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "move_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "resolve_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "schedule_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "set_issue_security",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "transition_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "unarchive_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_voters_and_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-1",
- "FullyQualifiedName": "Task/10033",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "My First Task"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "work_on_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "add_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assign_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "close_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "link_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "modify_reporter",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "move_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "resolve_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "schedule_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "set_issue_security",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "transition_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "unarchive_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_voters_and_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-3",
- "FullyQualifiedName": "Task/10035",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "backend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "work_on_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "add_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assign_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "close_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "link_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "modify_reporter",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "move_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "resolve_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "schedule_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "set_issue_security",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "transition_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "unarchive_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_voters_and_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-4",
- "FullyQualifiedName": "Task/10036",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "frontend functionality for Notification feature"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "work_on_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "add_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "administer",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assign_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "close_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_attachments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_all_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_comments",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "edit_own_worklogs",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "link_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "manage_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "modify_reporter",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "move_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "resolve_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "schedule_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "set_issue_security",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "transition_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "unarchive_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "view_voters_and_watchers",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "SCRUM-7",
- "FullyQualifiedName": "Task/10039",
- "Type": "Task",
- "Metadata": {
- "Project": "SCRUM",
- "Status": "To Do",
- "Summary": "Issue created via API"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "work_on_issues",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Shahzad Haider",
- "FullyQualifiedName": "User/712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Type": "User",
- "Metadata": {
- "AccountType": "atlassian",
- "Active": "true",
- "Email": "shahzadhaider@folio3.com",
- "SelfURL": "https://shaider.atlassian.net/rest/api/3/user?accountId=712020:71808a62-5c16-479c-9ebd-35742afb57fa"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assignable_user",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Shahzad Haider",
- "FullyQualifiedName": "User/712020:71808a62-5c16-479c-9ebd-35742afb57fa",
- "Type": "User",
- "Metadata": {
- "AccountType": "atlassian",
- "Active": "true",
- "Email": "shahzadhaider@folio3.com",
- "SelfURL": "https://shaider.atlassian.net/rest/api/3/user?accountId=712020:71808a62-5c16-479c-9ebd-35742afb57fa"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "user_picker",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {
-
- }
- }
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/launchdarkly/launchdarkly.go b/pkg/analyzer/analyzers/launchdarkly/launchdarkly.go
deleted file mode 100644
index 91dea5dd5efd..000000000000
--- a/pkg/analyzer/analyzers/launchdarkly/launchdarkly.go
+++ /dev/null
@@ -1,221 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go launchdarkly
-package launchdarkly
-
-import (
- "errors"
- "fmt"
- "os"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeLaunchDarkly
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- // check if the `key` exist in the credentials info
- key, exist := credInfo["key"]
- if !exist {
- return nil, errors.New("key not found in credentials info")
- }
-
- if isSDKKey(key) {
- return nil, errors.New("sdk keys cannot be analyzed")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, token string) {
- if isSDKKey(token) {
- color.Yellow("\n[!] The Provided key is an SDK Key. SDK Keys are sensitive but used to configure LaunchDarkly SDKs")
- color.Green("\n[i] Docs: https://launchdarkly.com/docs/home/account/environment/settings#copy-and-reset-sdk-credentials-for-an-environment")
-
- return
- }
-
- info, err := AnalyzePermissions(cfg, token)
- if err != nil {
- // just print the error in cli and continue as a partial success
- color.Red("[x] Error : %s", err.Error())
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- color.Green("[i] Valid LaunchDarkly Token\n")
- printUser(info.User)
- printPermissionsType(info.User.Token)
- printResources(info.Resources)
-
- color.Yellow("\n[!] Expires: Never")
-}
-
-// AnalyzePermissions will collect all the scopes assigned to token along with resource it can access
-func AnalyzePermissions(cfg *config.Config, token string) (*SecretInfo, error) {
- // create the http client
- client := analyzers.NewAnalyzeClient(cfg)
-
- var secretInfo = &SecretInfo{}
-
- // capture user information in secretInfo
- if err := CaptureUserInformation(client, token, secretInfo); err != nil {
- return nil, fmt.Errorf("failed to fetch caller identity: %v", err)
- }
-
- // capture resources in secretInfo
- if err := CaptureResources(client, token, secretInfo); err != nil {
- return nil, fmt.Errorf("failed to fetch resources: %v", err)
- }
-
- return secretInfo, nil
-}
-
-// secretInfoToAnalyzerResult translate secret info to Analyzer Result
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeLaunchDarkly,
- Metadata: map[string]any{},
- Bindings: make([]analyzers.Binding, 0),
- }
-
- // extract information from resource to create bindings and append to result bindings
- for _, resource := range info.Resources {
- binding := analyzers.Binding{
- Resource: *secretInfoResourceToAnalyzerResource(resource),
- Permission: analyzers.Permission{
- Value: getPermissionType(info.User.Token),
- },
- }
-
- if resource.ParentResource != nil {
- binding.Resource.Parent = secretInfoResourceToAnalyzerResource(*resource.ParentResource)
- }
-
- result.Bindings = append(result.Bindings, binding)
-
- }
-
- return &result
-}
-
-// secretInfoResourceToAnalyzerResource translate secret info resource to analyzer resource for binding
-func secretInfoResourceToAnalyzerResource(resource Resource) *analyzers.Resource {
- analyzerRes := analyzers.Resource{
- FullyQualifiedName: resource.ID,
- Name: resource.Name,
- Type: resource.Type,
- Metadata: map[string]any{},
- }
-
- for key, value := range resource.MetaData {
- analyzerRes.Metadata[key] = value
- }
-
- return &analyzerRes
-}
-
-// getPermissionType return what type of permission is assigned to token
-func getPermissionType(token Token) string {
- switch {
- case token.Role != "":
- return token.Role
- case token.hasInlineRole():
- return "Inline Policy"
- case token.hasCustomRoles():
- return "Custom Roles"
- default:
- return ""
- }
-}
-
-// printUser print User information from secret info to cli
-func printUser(user User) {
- // print caller information
- color.Green("\n[i] User Information:")
- callerTable := table.NewWriter()
- callerTable.SetOutputMirror(os.Stdout)
- callerTable.AppendHeader(table.Row{"Account ID", "Member ID", "Name", "Email", "Role"})
- callerTable.AppendRow(table.Row{color.GreenString(user.AccountID), color.GreenString(user.MemberID),
- color.GreenString(user.Name), color.GreenString(user.Email), color.GreenString(user.Role)})
-
- callerTable.Render()
-
- // print token information
- color.Green("\n[i] Token Information")
- tokenTable := table.NewWriter()
- tokenTable.SetOutputMirror(os.Stdout)
-
- tokenTable.AppendHeader(table.Row{"ID", "Name", "Role", "Is Service Token", "Default API Version",
- "No of Custom Roles Assigned", "Has Inline Policy"})
-
- tokenTable.AppendRow(table.Row{color.GreenString(user.Token.ID), color.GreenString(user.Token.Name), color.GreenString(user.Token.Role),
- color.GreenString(fmt.Sprintf("%t", user.Token.IsServiceToken)), color.GreenString(fmt.Sprintf("%d", user.Token.APIVersion)),
- color.GreenString(fmt.Sprintf("%d", len(user.Token.CustomRoles))), color.GreenString(fmt.Sprintf("%t", user.Token.hasInlineRole()))})
-
- tokenTable.Render()
-
- // print custom roles information
- if !user.Token.hasCustomRoles() {
- return
- }
-
- // print token information
- color.Green("\n[i] Custom Roles Assigned to Token")
- rolesTable := table.NewWriter()
- rolesTable.SetOutputMirror(os.Stdout)
- rolesTable.AppendHeader(table.Row{"ID", "Key", "Name", "Base Permission", "Assigned to members", "Assigned to teams"})
- for _, customRole := range user.Token.CustomRoles {
- rolesTable.AppendRow(table.Row{color.GreenString(customRole.ID), color.GreenString(customRole.Key), color.GreenString(customRole.Name),
- color.GreenString(customRole.BasePermission), color.GreenString(fmt.Sprintf("%d", customRole.AssignedToMembers)),
- color.GreenString(fmt.Sprintf("%d", customRole.AssignedToTeams))})
- }
- rolesTable.Render()
-}
-
-// printPermissionsType print permissions type token has
-func printPermissionsType(token Token) {
- // print permission type. It can be either admin, writer, reader or has inline policy or any custom roles assigned
- color.Green("\n[i] Permission Type: %s", getPermissionType(token))
-}
-
-func printResources(resources []Resource) {
- // print resources
- color.Green("\n[i] Resources:")
- callerTable := table.NewWriter()
- callerTable.SetOutputMirror(os.Stdout)
- callerTable.AppendHeader(table.Row{"Name", "Type"})
- for _, resource := range resources {
- callerTable.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})
- }
- callerTable.Render()
-}
-
-// isSDKKey check if the key provided is an SDK Key or not
-func isSDKKey(key string) bool {
- return strings.HasPrefix(key, "sdk-")
-}
diff --git a/pkg/analyzer/analyzers/launchdarkly/launchdarkly_test.go b/pkg/analyzer/analyzers/launchdarkly/launchdarkly_test.go
deleted file mode 100644
index 7b9ccf7f12a0..000000000000
--- a/pkg/analyzer/analyzers/launchdarkly/launchdarkly_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package launchdarkly
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed result_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- key := testSecrets.MustGetField("LAUNCHDARKLY_TOKEN")
-
- tests := []struct {
- name string
- key string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid LaunchDarkly token",
- key: key,
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/launchdarkly/models.go b/pkg/analyzer/analyzers/launchdarkly/models.go
deleted file mode 100644
index 9941522aa675..000000000000
--- a/pkg/analyzer/analyzers/launchdarkly/models.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package launchdarkly
-
-import "sync"
-
-var (
- MetadataKey = "key"
-
- // resource types
- applicationKey = "Application"
- repositoryKey = "Repository"
- projectKey = "Project"
- environmentKey = "Environment"
- experimentKey = "Experiment"
- holdoutsKey = "Holdout"
- membersKey = "Member"
- destinationsKey = "Destination"
- templatesKey = "Templates"
- teamsKey = "Teams"
- webhooksKey = "Webhooks"
- featureFlagsKey = "Feature Flags"
-)
-
-type SecretInfo struct {
- User User
- Permissions []string
-
- mu sync.RWMutex
- Resources []Resource
-}
-
-// User is the information about the user to whom the token belongs
-type User struct {
- AccountID string // account id. It is the owner id of token as well
- MemberID string
- Name string
- Role string // role of caller
- Email string
- Token Token
-}
-
-// Token is the token details
-type Token struct {
- ID string // id of the token
- Name string // name of the token
- CustomRoles []CustomRole // custom roles assigned to the token
- InlineRole []Policy // any policy statements maybe used in place of a built-in custom role
- Role string // role of token
- IsServiceToken bool // is a service token or not
- APIVersion int // default api version assigned to the token
-}
-
-// CustomRole is a flexible policies providing fine-grained access control to everything in launch darkly
-type CustomRole struct {
- ID string
- Key string
- Name string
- Polices []Policy
- BasePermission string
- AssignedToMembers int
- AssignedToTeams int
-}
-
-// policy is a set of statements
-type Policy struct {
- Resources []string
- NotResources []string
- Actions []string
- NotActions []string
- Effect string
-}
-
-type Resource struct {
- ID string
- Name string
- Permission string
- Type string
- ParentResource *Resource
- MetaData map[string]string
-}
-
-// appendResource append resource to secret info resources list
-func (s *SecretInfo) appendResource(resource Resource) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- s.Resources = append(s.Resources, resource)
-}
-
-// listResourceByType returns a list of resources matching the given type.
-func (s *SecretInfo) listResourceByType(resourceType string) []Resource {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- resources := make([]Resource, 0, len(s.Resources))
- for _, resource := range s.Resources {
- if resource.Type == resourceType {
- resources = append(resources, resource)
- }
- }
-
- return resources
-}
-
-// hasCustomRoles check if token has any custom roles assigned
-func (t Token) hasCustomRoles() bool {
- return len(t.CustomRoles) > 0
-}
-
-// hasInlineRole check if token has any inline roles
-func (t Token) hasInlineRole() bool {
- return len(t.InlineRole) > 0
-}
diff --git a/pkg/analyzer/analyzers/launchdarkly/permissions.go b/pkg/analyzer/analyzers/launchdarkly/permissions.go
deleted file mode 100644
index f4e3b62822fd..000000000000
--- a/pkg/analyzer/analyzers/launchdarkly/permissions.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package launchdarkly
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Admin Permission = iota
- Writer Permission = iota
- Reader Permission = iota
- Inlinepolicy Permission = iota
- Customroles Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Admin: "admin",
- Writer: "writer",
- Reader: "reader",
- Inlinepolicy: "inlinepolicy",
- Customroles: "customroles",
- }
-
- StringToPermission = map[string]Permission{
- "admin": Admin,
- "writer": Writer,
- "reader": Reader,
- "inlinepolicy": Inlinepolicy,
- "customroles": Customroles,
- }
-
- PermissionIDs = map[Permission]int{
- Admin: 1,
- Writer: 2,
- Reader: 3,
- Inlinepolicy: 4,
- Customroles: 5,
- }
-
- IdToPermission = map[int]Permission{
- 1: Admin,
- 2: Writer,
- 3: Reader,
- 4: Inlinepolicy,
- 5: Customroles,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/launchdarkly/permissions.yaml b/pkg/analyzer/analyzers/launchdarkly/permissions.yaml
deleted file mode 100644
index 8e1dade38d1e..000000000000
--- a/pkg/analyzer/analyzers/launchdarkly/permissions.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-permissions:
- - admin
- - writer
- - reader
- - inlinepolicy
- - customroles
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/launchdarkly/requests.go b/pkg/analyzer/analyzers/launchdarkly/requests.go
deleted file mode 100644
index 3d0996d0b071..000000000000
--- a/pkg/analyzer/analyzers/launchdarkly/requests.go
+++ /dev/null
@@ -1,741 +0,0 @@
-package launchdarkly
-
-import (
- "context"
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strconv"
- "sync"
- "time"
-)
-
-const defaultTimeout = 5 * time.Second
-
-var (
- baseURL = "https://app.launchdarkly.com/api"
-
- endpoints = map[string]string{
- // user information APIs
- "callerIdentity": "/v2/caller-identity",
- "getToken": "/v2/tokens/%s", // require token id
- "getRole": "/v2/roles/%s", // require role id
- // resource APIs
- applicationKey: "/v2/applications",
- repositoryKey: "/v2/code-refs/repositories",
- projectKey: "/v2/projects",
- environmentKey: "/v2/projects/%s/environments", // require project key
- featureFlagsKey: "/v2/flags/%s", // require project key
- experimentKey: "/v2/projects/%s/environments/%s/experiments", // require project key and env key
- holdoutsKey: "/v2/projects/%s/environments/%s/holdouts", // require project key and env key
- membersKey: "/v2/members",
- destinationsKey: "/v2/destinations",
- templatesKey: "/v2/templates",
- teamsKey: "/v2/teams",
- webhooksKey: "/v2/webhooks",
- /*
- TODO:
- release pipelines: https://launchdarkly.com/docs/api/release-pipelines-beta/get-all-release-pipelines (Beta)
- insight deployments: https://launchdarkly.com/docs/api/insights-deployments-beta/get-deployments (Beta)
- delivery configuration: https://launchdarkly.com/docs/api/integration-delivery-configurations-beta/get-integration-delivery-configuration-by-environment (Beta)
- metrics: https://launchdarkly.com/docs/api/metrics-beta/get-metric-groups (Beta)
- */
- }
-)
-
-// applicationsResponse is the response of /v2/applications API
-type applicationsResponse struct {
- Items []struct {
- Key string `json:"key"`
- Name string `json:"name"`
- Kind string `json:"kind"`
- Maintainer struct {
- Email string `json:"email"`
- } `json:"_maintainer"`
- } `json:"items"`
-}
-
-// repositoriesResponse is the response of /v2/code-refs/repositories API
-type repositoriesResponse struct {
- Items []struct {
- Name string `json:"name"`
- Type string `json:"type"`
- DefaultBranch string `json:"defaultBranch"`
- SourceLink string `json:"sourceLink"`
- Version int `json:"version"`
- } `json:"items"`
-}
-
-// projectsResponse is the response of /v2/projects API
-type projectsResponse struct {
- Items []struct {
- ID string `json:"_id"`
- Key string `json:"key"`
- Name string `json:"name"`
- } `json:"items"`
-}
-
-// featureFlagsResponse is the response of /v2/flags/ API
-type featureFlagsResponse struct {
- Items []struct {
- Key string `json:"key"`
- Name string `json:"name"`
- Kind string `json:"kind"`
- } `json:"items"`
-}
-
-// environmentsResponse is the response of /v2/projects//environments API
-type environmentsResponse struct {
- Items []struct {
- ID string `json:"_id"`
- Key string `json:"key"`
- Name string `json:"name"`
- } `json:"items"`
-}
-
-// experimentResponse is the response of /v2/projects//env//experiments
-type experimentResponse struct {
- Items []struct {
- ID string `json:"_id"`
- Key string `json:"key"`
- Name string `json:"name"`
- MaintainerID string `json:"_maintainerId"`
- } `json:"items"`
-}
-
-// membersResponse is the response of /v2/members API
-type membersResponse struct {
- Items []struct {
- ID string `json:"_id"`
- Role string `json:"role"`
- Email string `json:"email"`
- FirstName string `json:"firstName"`
- LastName string `json:"lastName"`
- } `json:"items"`
-}
-
-// holdoutsResponse is the response of /v2/projects//environments//holdouts API
-type holdoutsResponse struct {
- Items []struct {
- ID string `json:"_id"`
- Name string `json:"name"`
- Key string `json:"key"`
- Status string `json:"status"`
- } `json:"items"`
-}
-
-// destinationsResponse is the response of /v2/destinations API
-type destinationsResponse struct {
- Items []struct {
- ID string `json:"_id"`
- Name string `json:"name"`
- Kind string `json:"kind"`
- Version int `json:"version"`
- } `json:"items"`
-}
-
-// templatesResponse is the response of /v2/templates API
-type templatesResponse struct {
- Items []struct {
- ID string `json:"_id"`
- Key string `json:"_key"`
- Name string `json:"name"`
- } `json:"items"`
-}
-
-// teamsResponse is the response of /v2/teams API
-type teamsResponse struct {
- Items []struct {
- Key string `json:"key"`
- Name string `json:"name"`
- Roles struct {
- TotalCount int `json:"totalCount"`
- } `json:"roles"`
- Members struct {
- TotalCount int `json:"totalCount"`
- } `json:"members"`
- Projects struct {
- TotalCount int `json:"totalCount"`
- } `json:"projects"`
- } `json:"items"`
-}
-
-// webhooksResponse is the response of /v2/webhooks API
-type webhooksResponse struct {
- Items []struct {
- ID string `json:"_id"`
- Name string `json:"name"`
- Url string `json:"url"`
- } `json:"items"`
-}
-
-// makeLaunchDarklyRequest send the HTTP GET API request to passed url with passed token and return response body and status code
-func makeLaunchDarklyRequest(client *http.Client, endpoint, token string) ([]byte, int, error) {
- ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
- defer cancel()
-
- // create request
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+endpoint, http.NoBody)
- if err != nil {
- return nil, 0, err
- }
-
- // add required keys in the header
- req.Header.Set("Authorization", token)
- req.Header.Set("Content-Type", "application/json")
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, 0, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- responseBodyByte, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, 0, err
- }
-
- return responseBodyByte, resp.StatusCode, nil
-}
-
-func CaptureResources(client *http.Client, token string, secretInfo *SecretInfo) error {
- var (
- wg sync.WaitGroup
- errAggWg sync.WaitGroup
- aggregatedErrs = make([]error, 0)
- errChan = make(chan error, 1)
- )
-
- errAggWg.Add(1)
- go func() {
- defer errAggWg.Done()
- for err := range errChan {
- aggregatedErrs = append(aggregatedErrs, err)
- }
- }()
-
- // helper to launch tasks concurrently.
- launchTask := func(task func() error) {
- wg.Add(1)
- go func() {
- defer wg.Done()
- if err := task(); err != nil {
- errChan <- err
- }
- }()
- }
-
- // capture top-level resources
- launchTask(func() error { return captureApplications(client, token, secretInfo) })
- launchTask(func() error { return captureRepositories(client, token, secretInfo) })
-
- // capture projects
- launchTask(func() error {
- if err := captureProjects(client, token, secretInfo); err != nil {
- return err
- }
-
- // capture project sub resources
- projects := secretInfo.listResourceByType(projectKey)
- for _, proj := range projects {
- launchTask(func() error { return captureProjectFeatureFlags(client, token, proj, secretInfo) })
- launchTask(func() error { return captureProjectEnv(client, token, proj, secretInfo) })
- }
-
- return nil
- })
-
- launchTask(func() error { return captureMembers(client, token, secretInfo) })
- launchTask(func() error { return captureDestinations(client, token, secretInfo) })
- launchTask(func() error { return captureTemplates(client, token, secretInfo) })
- launchTask(func() error { return captureTeams(client, token, secretInfo) })
- launchTask(func() error { return captureWebhooks(client, token, secretInfo) })
-
- wg.Wait()
- close(errChan)
- errAggWg.Wait()
-
- if len(aggregatedErrs) > 0 {
- return errors.Join(aggregatedErrs...)
- }
-
- return nil
-}
-
-// docs: https://launchdarkly.com/docs/api/applications-beta/get-applications
-func captureApplications(client *http.Client, token string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeLaunchDarklyRequest(client, endpoints[applicationKey], token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var applications = applicationsResponse{}
-
- if err := json.Unmarshal(response, &applications); err != nil {
- return err
- }
-
- for _, application := range applications.Items {
- resource := Resource{
- ID: fmt.Sprintf("launchdarkly/app/%s", application.Key),
- Name: application.Name,
- Type: applicationKey,
- MetaData: map[string]string{
- "Maintainer Email": application.Maintainer.Email,
- "Kind": application.Kind,
- MetadataKey: application.Key,
- },
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://launchdarkly.com/docs/api/code-references/get-repositories
-func captureRepositories(client *http.Client, token string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeLaunchDarklyRequest(client, endpoints[repositoryKey], token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var repositories = repositoriesResponse{}
-
- if err := json.Unmarshal(response, &repositories); err != nil {
- return err
- }
-
- for _, repository := range repositories.Items {
- resource := Resource{
- ID: fmt.Sprintf("%s/repo/%s/%d", repository.Type, repository.Name, repository.Version), // no unique id exist, so we make one
- Name: repository.Name,
- Type: repositoryKey,
- MetaData: map[string]string{
- "Default branch": repository.DefaultBranch,
- "Version": strconv.Itoa(repository.Version),
- "Source link": repository.SourceLink,
- MetadataKey: repositoryKey,
- },
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://launchdarkly.com/docs/api/projects/get-projects
-func captureProjects(client *http.Client, token string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeLaunchDarklyRequest(client, endpoints[projectKey], token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var projects = projectsResponse{}
-
- if err := json.Unmarshal(response, &projects); err != nil {
- return err
- }
-
- for _, project := range projects.Items {
- secretInfo.appendResource(Resource{
- ID: fmt.Sprintf("launchdarkly/proj/%s", project.ID),
- Name: project.Name,
- Type: projectKey,
- MetaData: map[string]string{
- MetadataKey: project.Key,
- },
- })
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://launchdarkly.com/docs/api/feature-flags/get-feature-flags
-func captureProjectFeatureFlags(client *http.Client, token string, parent Resource, secretInfo *SecretInfo) error {
- projectKey, exist := parent.MetaData[MetadataKey]
- if !exist {
- return errors.New("project key not found")
- }
-
- response, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints[featureFlagsKey], projectKey), token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var flags = featureFlagsResponse{}
-
- if err := json.Unmarshal(response, &flags); err != nil {
- return err
- }
-
- for _, flag := range flags.Items {
- resource := Resource{
- ID: fmt.Sprintf("launchdarkly/proj/%s/flag/%s", projectKey, flag.Key),
- Name: flag.Name,
- Type: featureFlagsKey,
- MetaData: map[string]string{
- "Kind": flag.Kind,
- },
- ParentResource: &parent,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://launchdarkly.com/docs/api/environments/get-environments-by-project
-func captureProjectEnv(client *http.Client, token string, parent Resource, secretInfo *SecretInfo) error {
- projectKey, exist := parent.MetaData[MetadataKey]
- if !exist {
- return errors.New("project key not found")
- }
-
- response, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints[environmentKey], projectKey), token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var envs = environmentsResponse{}
-
- if err := json.Unmarshal(response, &envs); err != nil {
- return err
- }
-
- for _, env := range envs.Items {
- resource := Resource{
- ID: fmt.Sprintf("launchdarkly/%s/env/%s", projectKey, env.ID),
- Name: env.Name,
- Type: environmentKey,
- MetaData: map[string]string{
- MetadataKey: env.Key,
- },
- ParentResource: &parent,
- }
-
- secretInfo.appendResource(resource)
-
- // capture project env child resources
- if err := captureProjectEnvExperiments(client, token, projectKey, resource, secretInfo); err != nil {
- return err
- }
-
- if err := captureProjectHoldouts(client, token, projectKey, resource, secretInfo); err != nil {
- return err
- }
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://launchdarkly.com/docs/api/experiments/get-experiments
-func captureProjectEnvExperiments(client *http.Client, token string, projectKey string, parent Resource, secretInfo *SecretInfo) error {
- envKey, exist := parent.MetaData[MetadataKey]
- if !exist {
- return errors.New("env key not found")
- }
-
- response, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints[experimentKey], projectKey, envKey), token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var exps = experimentResponse{}
-
- if err := json.Unmarshal(response, &exps); err != nil {
- return err
- }
-
- for _, exp := range exps.Items {
- resource := Resource{
- ID: fmt.Sprintf("launchdarkly/%s/env/%s/exp/%s", projectKey, envKey, exp.ID),
- Name: exp.Name,
- Type: experimentKey,
- MetaData: map[string]string{
- MetadataKey: exp.Key,
- "Maintiner ID": exp.MaintainerID,
- },
- ParentResource: &parent,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound:
- return nil
- case http.StatusTooManyRequests:
- time.Sleep(1 * time.Second)
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://launchdarkly.com/docs/api/holdouts-beta/get-all-holdouts
-func captureProjectHoldouts(client *http.Client, token string, projectKey string, parent Resource, secretInfo *SecretInfo) error {
- envKey, exist := parent.MetaData[MetadataKey]
- if !exist {
- return errors.New("env key not found")
- }
-
- response, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints[holdoutsKey], projectKey, envKey), token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var holdouts = holdoutsResponse{}
-
- if err := json.Unmarshal(response, &holdouts); err != nil {
- return err
- }
-
- for _, holdout := range holdouts.Items {
- resource := Resource{
- ID: fmt.Sprintf("launchdarkly/%s/env/%s/holdout/%s", projectKey, envKey, holdout.ID),
- Name: holdout.Name,
- Type: holdoutsKey,
- MetaData: map[string]string{
- "Status": holdout.Status,
- holdoutsKey: holdout.Key,
- },
- ParentResource: &parent,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://launchdarkly.com/docs/api/account-members/get-members
-func captureMembers(client *http.Client, token string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeLaunchDarklyRequest(client, endpoints[membersKey], token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var members = membersResponse{}
-
- if err := json.Unmarshal(response, &members); err != nil {
- return err
- }
-
- for _, member := range members.Items {
- resource := Resource{
- ID: fmt.Sprintf("launchdarkly/member/%s", member.ID),
- Name: member.FirstName + " " + member.LastName,
- Type: membersKey,
- MetaData: map[string]string{
- "Role": member.Role,
- "Email": member.Email,
- },
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://launchdarkly.com/docs/api/data-export-destinations/get-destinations
-func captureDestinations(client *http.Client, token string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeLaunchDarklyRequest(client, endpoints[destinationsKey], token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var destinations = destinationsResponse{}
-
- if err := json.Unmarshal(response, &destinations); err != nil {
- return err
- }
-
- for _, destination := range destinations.Items {
- resource := Resource{
- ID: fmt.Sprintf("launchdarkly/destination/%s", destination.ID),
- Name: destination.Name,
- Type: destinationsKey,
- MetaData: map[string]string{
- "Kind": destination.Kind,
- "Version": strconv.Itoa(destination.Version),
- },
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://launchdarkly.com/docs/api/workflow-templates/get-workflow-templates
-func captureTemplates(client *http.Client, token string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeLaunchDarklyRequest(client, endpoints[templatesKey], token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var templates = templatesResponse{}
-
- if err := json.Unmarshal(response, &templates); err != nil {
- return err
- }
-
- for _, template := range templates.Items {
- resource := Resource{
- ID: fmt.Sprintf("launchdarkly/templates/%s", template.ID),
- Name: template.Name,
- Type: templatesKey,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://launchdarkly.com/docs/api/teams/get-teams
-func captureTeams(client *http.Client, token string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeLaunchDarklyRequest(client, endpoints[teamsKey], token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var teams = teamsResponse{}
-
- if err := json.Unmarshal(response, &teams); err != nil {
- return err
- }
-
- for _, team := range teams.Items {
- resource := Resource{
- ID: fmt.Sprintf("launchdarkly/teams/%s", team.Key),
- Name: team.Name,
- Type: teamsKey,
- MetaData: map[string]string{
- "Total Roles Count": strconv.Itoa(team.Roles.TotalCount),
- "Total Memvers Count": strconv.Itoa(team.Members.TotalCount),
- "Total Projects Count": strconv.Itoa(team.Projects.TotalCount),
- },
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// docs: https://launchdarkly.com/docs/api/webhooks/get-all-webhooks
-func captureWebhooks(client *http.Client, token string, secretInfo *SecretInfo) error {
- response, statusCode, err := makeLaunchDarklyRequest(client, endpoints[webhooksKey], token)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var webhooks = webhooksResponse{}
-
- if err := json.Unmarshal(response, &webhooks); err != nil {
- return err
- }
-
- for _, webhook := range webhooks.Items {
- resource := Resource{
- ID: fmt.Sprintf("launchdarkly/webhooks/%s", webhook.ID),
- Name: webhook.Name,
- Type: webhooksKey,
- }
-
- secretInfo.appendResource(resource)
- }
-
- return nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
diff --git a/pkg/analyzer/analyzers/launchdarkly/result_output.json b/pkg/analyzer/analyzers/launchdarkly/result_output.json
deleted file mode 100644
index 8570eaa3d11b..000000000000
--- a/pkg/analyzer/analyzers/launchdarkly/result_output.json
+++ /dev/null
@@ -1,107 +0,0 @@
-{
- "AnalyzerType": 31,
- "Bindings": [
- {
- "Resource": {
- "Name": "Production",
- "FullyQualifiedName": "launchdarkly/default/env/61543c5956be602355624871",
- "Type": "Environment",
- "Metadata": {
- "key": "production"
- },
- "Parent": {
- "Name": "secretscanner",
- "FullyQualifiedName": "launchdarkly/proj/61543c5956be60235562486e",
- "Type": "Project",
- "Metadata": {
- "key": "default"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "admin",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Roxanne Tampus",
- "FullyQualifiedName": "launchdarkly/member/61543c5956be60235562486f",
- "Type": "Member",
- "Metadata": {
- "Email": "knightmoverchan@gmail.com",
- "Role": "owner"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "admin",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Test",
- "FullyQualifiedName": "launchdarkly/default/env/61543c5956be602355624870",
- "Type": "Environment",
- "Metadata": {
- "key": "test"
- },
- "Parent": {
- "Name": "secretscanner",
- "FullyQualifiedName": "launchdarkly/proj/61543c5956be60235562486e",
- "Type": "Project",
- "Metadata": {
- "key": "default"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "admin",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "secretscanner",
- "FullyQualifiedName": "launchdarkly/proj/61543c5956be60235562486e",
- "Type": "Project",
- "Metadata": {
- "key": "default"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "admin",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "secretscanner",
- "FullyQualifiedName": "launchdarkly/proj/default/flag/secretscanner",
- "Type": "Feature Flags",
- "Metadata": {
- "Kind": "boolean"
- },
- "Parent": {
- "Name": "secretscanner",
- "FullyQualifiedName": "launchdarkly/proj/61543c5956be60235562486e",
- "Type": "Project",
- "Metadata": {
- "key": "default"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "admin",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {}
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/launchdarkly/user.go b/pkg/analyzer/analyzers/launchdarkly/user.go
deleted file mode 100644
index 06c69f320242..000000000000
--- a/pkg/analyzer/analyzers/launchdarkly/user.go
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
-user.go file is all related to calling APIs to get user and token information and formatting them to secretInfo User.
-
-It calls 3 APIs:
- - /v2/caller-identity
- - /v2/tokens/ (with token id from previous api response)
- - /v2/roles/ (if custom role id is present in tokens) (more than one role can be assigned to token as well)
-
-it formats all these responses into one User struct for secretInfo.
-*/
-package launchdarkly
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
-)
-
-// callerIdentityResponse is /v2/caller-identity API response
-type callerIdentityResponse struct {
- AccountID string `json:"accountId"`
- TokenName string `json:"tokenName"`
- TokenID string `json:"tokenId"`
- MemberID string `json:"memberId"`
- ServiceToken bool `json:"serviceToken"`
-}
-
-// tokenResponse is the /v2/tokens/ API response
-type tokenResponse struct {
- OwnerID string `json:"ownerId"`
- Member tokenMemberResponse `json:"_member"`
- Name string `json:"name"`
- CustomRoleIDs []string `json:"customRoleIds,omitempty"`
- InlineRole []tokenPolicyResponse `json:"inlineRole,omitempty"`
- Role string `json:"role"`
- ServiceToken bool `json:"serviceToken"`
- DefaultAPIVersion int `json:"defaultApiVersion"`
-}
-
-// _member object in token response
-type tokenMemberResponse struct {
- FirstName string `json:"firstName"`
- LastName string `json:"lastName"`
- Role string `json:"role"`
- Email string `json:"email"`
-}
-
-// inlineRole object in token response
-type tokenPolicyResponse struct {
- Effect string `json:"effect,omitempty"`
- Resources []string `json:"resources,omitempty"`
- NotResources []string `json:"notResources,omitempty"`
- Actions []string `json:"actions,omitempty"`
- NotActions []string `json:"notActions,omitempty"`
-}
-
-// customRoleResponse is the /v2/roles/ API response
-type customRoleResponse struct {
- ID string `json:"_id"`
- Key string `json:"key"`
- Name string `json:"name"`
- Policy []tokenPolicyResponse `json:"policy"`
- BasePermission string `json:"basePermissions"`
- AssignedTo struct {
- MembersCount int `json:"membersCount"`
- TeamsCount int `json:"teamsCount"`
- } `json:"assignedTo"`
-}
-
-/*
-CaptureUserInformation call following three APIs:
- - /v2/caller-identity
- - /v2/tokens/ (token_id from previous API response)
- - /v2/roles/ (roles_id from previous API response if exist)
-
-It format all responses into one secret info User
-*/
-func CaptureUserInformation(client *http.Client, token string, secretInfo *SecretInfo) error {
- caller, err := getCallerIdentity(client, token)
- if err != nil {
- return err
- }
-
- tokenDetails, err := getToken(client, caller.TokenID, token)
- if err != nil {
- return err
- }
-
- customRoles, err := getCustomRole(client, tokenDetails.CustomRoleIDs, token)
- if err != nil {
- return err
- }
-
- addUserToSecretInfo(caller, tokenDetails, customRoles, secretInfo)
-
- return nil
-}
-
-// getCallerIdentity call /v2/caller-identity API and return response
-func getCallerIdentity(client *http.Client, token string) (*callerIdentityResponse, error) {
- response, statusCode, err := makeLaunchDarklyRequest(client, endpoints["callerIdentity"], token)
- if err != nil {
- return nil, err
- }
-
- switch statusCode {
- case http.StatusOK:
- var caller = &callerIdentityResponse{}
-
- if err := json.Unmarshal(response, caller); err != nil {
- return caller, err
- }
-
- return caller, nil
- case http.StatusUnauthorized:
- return nil, errors.New("invalid token; failed to get caller information")
- default:
- return nil, fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// getToken call /v2/tokens/ API and return response
-func getToken(client *http.Client, tokenID, token string) (*tokenResponse, error) {
- response, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints["getToken"], tokenID), token)
- if err != nil {
- return nil, err
- }
-
- switch statusCode {
- case http.StatusOK:
- var token tokenResponse
-
- if err := json.Unmarshal(response, &token); err != nil {
- return nil, err
- }
-
- return &token, nil
- case http.StatusUnauthorized:
- return nil, errors.New("invalid token; failed to get token information")
- default:
- return nil, fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-// getCustomRole call /v2/roles/ API for all IDs passed and return list of responses
-func getCustomRole(client *http.Client, customRoleIDs []string, token string) ([]customRoleResponse, error) {
- var customRoles []customRoleResponse
-
- for _, customRoleID := range customRoleIDs {
- response, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints["getRole"], customRoleID), token)
- if err != nil {
- return nil, err
- }
-
- switch statusCode {
- case http.StatusOK:
- var customRole customRoleResponse
-
- if err := json.Unmarshal(response, &customRole); err != nil {
- return nil, err
- }
-
- customRoles = append(customRoles, customRole)
- case http.StatusUnauthorized:
- return nil, nil
- default:
- return nil, fmt.Errorf("unexpected status code: %d", statusCode)
- }
- }
-
- return customRoles, nil
-}
-
-// makeCallerIdentity take caller, tokenDetails, and customRoles and return secret info CallerIdentity
-func addUserToSecretInfo(caller *callerIdentityResponse, tokenDetails *tokenResponse, customRoles []customRoleResponse, secretInfo *SecretInfo) {
- user := User{
- AccountID: caller.AccountID,
- MemberID: caller.MemberID,
- Name: tokenDetails.Member.FirstName + " " + tokenDetails.Member.LastName,
- Role: tokenDetails.Member.Role,
- Email: tokenDetails.Member.Email,
- Token: Token{
- ID: caller.TokenID,
- Name: tokenDetails.Name,
- Role: tokenDetails.Role,
- APIVersion: tokenDetails.DefaultAPIVersion,
- IsServiceToken: tokenDetails.ServiceToken,
- InlineRole: toPolicy(tokenDetails.InlineRole),
- CustomRoles: toCustomRoles(customRoles),
- },
- }
-
- secretInfo.User = user
-}
-
-// toPolicy convert inlinePolicy from token response to secret info caller identity policy
-func toPolicy(inlinePolices []tokenPolicyResponse) []Policy {
- var policies = make([]Policy, 0)
-
- for _, inlinePolicy := range inlinePolices {
- policies = append(policies, Policy{
- Resources: inlinePolicy.Resources,
- NotResources: inlinePolicy.NotResources,
- Actions: inlinePolicy.Actions,
- NotActions: inlinePolicy.NotActions,
- Effect: inlinePolicy.Effect,
- })
- }
-
- return policies
-}
-
-// toCustomRoles convert customRole from token response to secret info caller identity custom role
-func toCustomRoles(roles []customRoleResponse) []CustomRole {
- var customRoles = make([]CustomRole, 0)
- for _, role := range roles {
- customRoles = append(customRoles, CustomRole{
- ID: role.ID,
- Key: role.Key,
- Name: role.Name,
- Polices: toPolicy(role.Policy),
- BasePermission: role.BasePermission,
- AssignedToMembers: role.AssignedTo.MembersCount,
- AssignedToTeams: role.AssignedTo.TeamsCount,
- })
- }
-
- return customRoles
-}
diff --git a/pkg/analyzer/analyzers/mailchimp/expected_output.json b/pkg/analyzer/analyzers/mailchimp/expected_output.json
deleted file mode 100644
index 1e2054576031..000000000000
--- a/pkg/analyzer/analyzers/mailchimp/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":7,"Bindings":[{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"account_export","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"add_contacts","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"add_files_to_content_studio","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"add_or_access_api_keys","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"archive_contacts","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"audience_export","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"audience_import","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"change_billing_information","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"change_company_organization_name","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"check_reconnect_integrations","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"close_account","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"connect_a_domain","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"create_a_landing_page","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"create_audiences","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"create_customer_journey","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"create_emails","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"create_form","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"create_or_import_templates","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"create_send_sms_mms_messages","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"create_survey","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"create_your_website","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"delete_contacts","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"delete_emails","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"delete_form","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"delete_survey","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"domain_performance","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"e_commerce_product_activity","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"edit_audience_settings","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"edit_customer_journey","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"edit_emails","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"edit_form","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"edit_survey","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"edit_templates","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"email_contact_details","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"email_open_details","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"invite_users","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"leave_comments","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"opt_in_to_receive_emails_from_mailchimp","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"pause_unpublish_emails","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"publish_form","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"publish_survey","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"publish_unpublish_a_landing_page","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"publish_unpublish_your_website","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"purchase_sms_credits","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"referral_program","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"replicate_a_landing_page","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"require_2_factor_authentication","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"revoke_account_access","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"send_messages","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"send_publish_emails","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"set_user_access_level","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"submit_sms_marketing_application","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"toggle_user_notifications","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"top_locations","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"turn_on_pause_turn_back_on","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"use_conversations","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"verify_a_domain","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"view_abuse_reports","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"view_audiences","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"view_customer_journey","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"view_email_recipients","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"view_email_reports","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"view_email_statistics","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"view_messages","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"view_report","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"view_segments","Parent":null}},{"Resource":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null},"Permission":{"Value":"view_sms_reports","Parent":null}}],"UnboundedResources":[{"Name":"trufflesec.com","FullyQualifiedName":"mailchimp.com/domain/trufflesec.com","Type":"domain","Metadata":{"authenticated":false,"verified":true},"Parent":{"Name":"TruffleHog","FullyQualifiedName":"mailchimp.com/account/09f02f6ec9b78ff5c3ce52f96","Type":"account","Metadata":{"account_timezone":"America/New_York","email":"detectors@trufflesec.com","last_login":"2024-08-16T10:39:15+00:00","member_since":"2024-08-16T10:37:37+00:00","pricing_plan":"forever_free","role":"owner","total_subscribers":1},"Parent":null}}],"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/mailchimp/mailchimp.go b/pkg/analyzer/analyzers/mailchimp/mailchimp.go
deleted file mode 100644
index ca775db4fdaf..000000000000
--- a/pkg/analyzer/analyzers/mailchimp/mailchimp.go
+++ /dev/null
@@ -1,274 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go mailchimp
-package mailchimp
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "os"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-const BASE_URL = "https://%s.api.mailchimp.com/3.0"
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeMailchimp }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("key not found in credentialInfo")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeMailchimp,
- Bindings: make([]analyzers.Binding, 0, len(StringToPermission)),
- UnboundedResources: make([]analyzers.Resource, 0, len(info.Domains.Domains)),
- }
-
- accountResource := analyzers.Resource{
- Name: info.Metadata.AccountName,
- FullyQualifiedName: "mailchimp.com/account/" + info.Metadata.AccountID,
- Type: "account",
- Metadata: map[string]any{
- "email": info.Metadata.Email,
- "role": info.Metadata.Role,
- "member_since": info.Metadata.MemberSince,
- "pricing_plan": info.Metadata.PricingPlan,
- "account_timezone": info.Metadata.AccountTimezone,
- "last_login": info.Metadata.LastLogin,
- "total_subscribers": info.Metadata.TotalSubscribers,
- },
- }
-
- for perm := range StringToPermission {
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: accountResource,
- Permission: analyzers.Permission{
- Value: perm,
- },
- })
- }
-
- for _, domain := range info.Domains.Domains {
- result.UnboundedResources = append(result.UnboundedResources, analyzers.Resource{
- Name: domain.Domain,
- FullyQualifiedName: "mailchimp.com/domain/" + domain.Domain,
- Type: "domain",
- Metadata: map[string]any{
- "verified": domain.Verified,
- "authenticated": domain.Authenticated,
- },
- Parent: &accountResource,
- })
- }
-
- return &result
-}
-
-type MetadataJSON struct {
- AccountID string `json:"account_id"`
- AccountName string `json:"account_name"`
- Email string `json:"email"`
- FirstName string `json:"first_name"`
- LastName string `json:"last_name"`
- Role string `json:"role"`
- MemberSince string `json:"member_since"`
- PricingPlan string `json:"pricing_plan_type"`
- AccountTimezone string `json:"account_timezone"`
- Contact struct {
- Company string `json:"company"`
- Address1 string `json:"addr1"`
- Address2 string `json:"addr2"`
- City string `json:"city"`
- State string `json:"state"`
- Zip string `json:"zip"`
- Country string `json:"country"`
- } `json:"contact"`
- LastLogin string `json:"last_login"`
- TotalSubscribers int `json:"total_subscribers"`
-}
-
-type DomainsJSON struct {
- Domains []Domain `json:"domains"`
-}
-
-type Domain struct {
- Domain string `json:"domain"`
- Authenticated bool `json:"authenticated"`
- Verified bool `json:"verified"`
-}
-
-func getMetadata(cfg *config.Config, key string) (MetadataJSON, error) {
- var metadata MetadataJSON
-
- // extract datacenter
- keySplit := strings.Split(key, "-")
- if len(keySplit) != 2 {
- return metadata, nil
- }
- datacenter := keySplit[1]
-
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", fmt.Sprintf(BASE_URL, datacenter), nil)
- if err != nil {
- return metadata, err
- }
-
- req.SetBasicAuth("anystring", key)
- resp, err := client.Do(req)
- if err != nil {
- return metadata, err
- }
-
- defer resp.Body.Close()
-
- if err := json.NewDecoder(resp.Body).Decode(&metadata); err != nil {
- return metadata, err
- }
-
- return metadata, nil
-}
-
-func getDomains(cfg *config.Config, key string) (DomainsJSON, error) {
- var domains DomainsJSON
-
- // extract datacenter
- keySplit := strings.Split(key, "-")
- if len(keySplit) != 2 {
- return domains, nil
- }
- datacenter := keySplit[1]
-
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", fmt.Sprintf(BASE_URL, datacenter)+"/verified-domains", nil)
- if err != nil {
- return domains, err
- }
-
- req.SetBasicAuth("anystring", key)
- resp, err := client.Do(req)
- if err != nil {
- return domains, err
- }
-
- defer resp.Body.Close()
-
- if err := json.NewDecoder(resp.Body).Decode(&domains); err != nil {
- return domains, err
- }
-
- return domains, nil
-}
-
-type SecretInfo struct {
- Metadata MetadataJSON
- Domains DomainsJSON
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- // get metadata
- metadata, err := getMetadata(cfg, key)
- if err != nil {
- return nil, err
- }
- if metadata.AccountID == "" {
- return nil, fmt.Errorf("Invalid Mailchimp API key")
- }
-
- // get sending domains
- domains, err := getDomains(cfg, key)
- if err != nil {
- return nil, err
- }
-
- return &SecretInfo{
- Metadata: metadata,
- Domains: domains,
- }, nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
-
- printMetadata(info.Metadata)
-
- // print full api key permissions
- color.Green("\n[i] Permissions: Full Access\n\n")
-
- // print sending domains
- if len(info.Domains.Domains) > 0 {
- printDomains(info.Domains)
- } else {
- color.Yellow("[i] No sending domains found\n")
- }
-}
-
-func printMetadata(metadata MetadataJSON) {
- color.Green("[!] Valid Mailchimp API key\n\n")
-
- // print table with account info
- color.Yellow("[i] Mailchimp Account Info:\n")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendRow([]any{("Account Name"), color.GreenString("%s", metadata.AccountName)})
- t.AppendRow([]any{("Company Name"), color.GreenString("%s", metadata.Contact.Company)})
- t.AppendRow([]any{("Address"), color.GreenString("%s %s\n%s, %s %s\n%s", metadata.Contact.Address1, metadata.Contact.Address2, metadata.Contact.City, metadata.Contact.State, metadata.Contact.Zip, metadata.Contact.Country)})
- t.AppendRow([]any{("Total Subscribers"), color.GreenString("%d", metadata.TotalSubscribers)})
- t.Render()
-
- // print user info
- color.Yellow("\n[i] Mailchimp User Info:\n")
- t = table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendRow([]any{("User Name"), color.GreenString("%s %s", metadata.FirstName, metadata.LastName)})
- t.AppendRow([]any{("User Email"), color.GreenString("%s", metadata.Email)})
- t.AppendRow([]any{("User Role"), color.GreenString("%s", metadata.Role)})
- t.AppendRow([]any{("Last Login"), color.GreenString("%s", metadata.LastLogin)})
- t.AppendRow([]any{("Member Since"), color.GreenString("%s", metadata.MemberSince)})
- t.Render()
-}
-
-func printDomains(domains DomainsJSON) {
- color.Yellow("\n[i] Sending Domains:\n")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Domain", "Enabled and Verified"})
- for _, domain := range domains.Domains {
- authenticated := ""
- if domain.Authenticated && domain.Verified {
- authenticated = color.GreenString("Yes")
- } else {
- authenticated = color.RedString("No")
- }
- t.AppendRow([]any{color.GreenString(domain.Domain), authenticated})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/mailchimp/mailchimp_test.go b/pkg/analyzer/analyzers/mailchimp/mailchimp_test.go
deleted file mode 100644
index 7ab5b8ac8ded..000000000000
--- a/pkg/analyzer/analyzers/mailchimp/mailchimp_test.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package mailchimp
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expected_output []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid Mailchimp key",
- key: testSecrets.MustGetField("MAILCHIMP"),
- want: string(expected_output),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.Marshal(got)
- // gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/mailchimp/permissions.go b/pkg/analyzer/analyzers/mailchimp/permissions.go
deleted file mode 100644
index 7972a5586f3e..000000000000
--- a/pkg/analyzer/analyzers/mailchimp/permissions.go
+++ /dev/null
@@ -1,391 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package mailchimp
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- InviteUsers Permission = iota
- RevokeAccountAccess Permission = iota
- SetUserAccessLevel Permission = iota
- Require2FactorAuthentication Permission = iota
- ChangeBillingInformation Permission = iota
- ChangeCompanyOrganizationName Permission = iota
- AddOrAccessApiKeys Permission = iota
- CheckReconnectIntegrations Permission = iota
- ReferralProgram Permission = iota
- AccountExport Permission = iota
- CloseAccount Permission = iota
- AddFilesToContentStudio Permission = iota
- OptInToReceiveEmailsFromMailchimp Permission = iota
- CreateAudiences Permission = iota
- ViewAudiences Permission = iota
- AudienceExport Permission = iota
- AudienceImport Permission = iota
- AddContacts Permission = iota
- DeleteContacts Permission = iota
- ViewSegments Permission = iota
- EditAudienceSettings Permission = iota
- ArchiveContacts Permission = iota
- CreateOrImportTemplates Permission = iota
- EditTemplates Permission = iota
- CreateEmails Permission = iota
- EditEmails Permission = iota
- SendPublishEmails Permission = iota
- PauseUnpublishEmails Permission = iota
- DeleteEmails Permission = iota
- SubmitSmsMarketingApplication Permission = iota
- CreateSendSmsMmsMessages Permission = iota
- PurchaseSmsCredits Permission = iota
- ViewEmailReports Permission = iota
- ViewSmsReports Permission = iota
- ViewAbuseReports Permission = iota
- ViewEmailStatistics Permission = iota
- UseConversations Permission = iota
- ViewEmailRecipients Permission = iota
- TopLocations Permission = iota
- EmailContactDetails Permission = iota
- EmailOpenDetails Permission = iota
- ECommerceProductActivity Permission = iota
- DomainPerformance Permission = iota
- CreateYourWebsite Permission = iota
- PublishUnpublishYourWebsite Permission = iota
- ViewReport Permission = iota
- CreateALandingPage Permission = iota
- PublishUnpublishALandingPage Permission = iota
- ReplicateALandingPage Permission = iota
- VerifyADomain Permission = iota
- ConnectADomain Permission = iota
- CreateCustomerJourney Permission = iota
- ViewCustomerJourney Permission = iota
- EditCustomerJourney Permission = iota
- TurnOnPauseTurnBackOn Permission = iota
- ViewMessages Permission = iota
- LeaveComments Permission = iota
- SendMessages Permission = iota
- ToggleUserNotifications Permission = iota
- CreateSurvey Permission = iota
- EditSurvey Permission = iota
- PublishSurvey Permission = iota
- DeleteSurvey Permission = iota
- CreateForm Permission = iota
- EditForm Permission = iota
- PublishForm Permission = iota
- DeleteForm Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- InviteUsers: "invite_users",
- RevokeAccountAccess: "revoke_account_access",
- SetUserAccessLevel: "set_user_access_level",
- Require2FactorAuthentication: "require_2_factor_authentication",
- ChangeBillingInformation: "change_billing_information",
- ChangeCompanyOrganizationName: "change_company_organization_name",
- AddOrAccessApiKeys: "add_or_access_api_keys",
- CheckReconnectIntegrations: "check_reconnect_integrations",
- ReferralProgram: "referral_program",
- AccountExport: "account_export",
- CloseAccount: "close_account",
- AddFilesToContentStudio: "add_files_to_content_studio",
- OptInToReceiveEmailsFromMailchimp: "opt_in_to_receive_emails_from_mailchimp",
- CreateAudiences: "create_audiences",
- ViewAudiences: "view_audiences",
- AudienceExport: "audience_export",
- AudienceImport: "audience_import",
- AddContacts: "add_contacts",
- DeleteContacts: "delete_contacts",
- ViewSegments: "view_segments",
- EditAudienceSettings: "edit_audience_settings",
- ArchiveContacts: "archive_contacts",
- CreateOrImportTemplates: "create_or_import_templates",
- EditTemplates: "edit_templates",
- CreateEmails: "create_emails",
- EditEmails: "edit_emails",
- SendPublishEmails: "send_publish_emails",
- PauseUnpublishEmails: "pause_unpublish_emails",
- DeleteEmails: "delete_emails",
- SubmitSmsMarketingApplication: "submit_sms_marketing_application",
- CreateSendSmsMmsMessages: "create_send_sms_mms_messages",
- PurchaseSmsCredits: "purchase_sms_credits",
- ViewEmailReports: "view_email_reports",
- ViewSmsReports: "view_sms_reports",
- ViewAbuseReports: "view_abuse_reports",
- ViewEmailStatistics: "view_email_statistics",
- UseConversations: "use_conversations",
- ViewEmailRecipients: "view_email_recipients",
- TopLocations: "top_locations",
- EmailContactDetails: "email_contact_details",
- EmailOpenDetails: "email_open_details",
- ECommerceProductActivity: "e_commerce_product_activity",
- DomainPerformance: "domain_performance",
- CreateYourWebsite: "create_your_website",
- PublishUnpublishYourWebsite: "publish_unpublish_your_website",
- ViewReport: "view_report",
- CreateALandingPage: "create_a_landing_page",
- PublishUnpublishALandingPage: "publish_unpublish_a_landing_page",
- ReplicateALandingPage: "replicate_a_landing_page",
- VerifyADomain: "verify_a_domain",
- ConnectADomain: "connect_a_domain",
- CreateCustomerJourney: "create_customer_journey",
- ViewCustomerJourney: "view_customer_journey",
- EditCustomerJourney: "edit_customer_journey",
- TurnOnPauseTurnBackOn: "turn_on_pause_turn_back_on",
- ViewMessages: "view_messages",
- LeaveComments: "leave_comments",
- SendMessages: "send_messages",
- ToggleUserNotifications: "toggle_user_notifications",
- CreateSurvey: "create_survey",
- EditSurvey: "edit_survey",
- PublishSurvey: "publish_survey",
- DeleteSurvey: "delete_survey",
- CreateForm: "create_form",
- EditForm: "edit_form",
- PublishForm: "publish_form",
- DeleteForm: "delete_form",
- }
-
- StringToPermission = map[string]Permission{
- "invite_users": InviteUsers,
- "revoke_account_access": RevokeAccountAccess,
- "set_user_access_level": SetUserAccessLevel,
- "require_2_factor_authentication": Require2FactorAuthentication,
- "change_billing_information": ChangeBillingInformation,
- "change_company_organization_name": ChangeCompanyOrganizationName,
- "add_or_access_api_keys": AddOrAccessApiKeys,
- "check_reconnect_integrations": CheckReconnectIntegrations,
- "referral_program": ReferralProgram,
- "account_export": AccountExport,
- "close_account": CloseAccount,
- "add_files_to_content_studio": AddFilesToContentStudio,
- "opt_in_to_receive_emails_from_mailchimp": OptInToReceiveEmailsFromMailchimp,
- "create_audiences": CreateAudiences,
- "view_audiences": ViewAudiences,
- "audience_export": AudienceExport,
- "audience_import": AudienceImport,
- "add_contacts": AddContacts,
- "delete_contacts": DeleteContacts,
- "view_segments": ViewSegments,
- "edit_audience_settings": EditAudienceSettings,
- "archive_contacts": ArchiveContacts,
- "create_or_import_templates": CreateOrImportTemplates,
- "edit_templates": EditTemplates,
- "create_emails": CreateEmails,
- "edit_emails": EditEmails,
- "send_publish_emails": SendPublishEmails,
- "pause_unpublish_emails": PauseUnpublishEmails,
- "delete_emails": DeleteEmails,
- "submit_sms_marketing_application": SubmitSmsMarketingApplication,
- "create_send_sms_mms_messages": CreateSendSmsMmsMessages,
- "purchase_sms_credits": PurchaseSmsCredits,
- "view_email_reports": ViewEmailReports,
- "view_sms_reports": ViewSmsReports,
- "view_abuse_reports": ViewAbuseReports,
- "view_email_statistics": ViewEmailStatistics,
- "use_conversations": UseConversations,
- "view_email_recipients": ViewEmailRecipients,
- "top_locations": TopLocations,
- "email_contact_details": EmailContactDetails,
- "email_open_details": EmailOpenDetails,
- "e_commerce_product_activity": ECommerceProductActivity,
- "domain_performance": DomainPerformance,
- "create_your_website": CreateYourWebsite,
- "publish_unpublish_your_website": PublishUnpublishYourWebsite,
- "view_report": ViewReport,
- "create_a_landing_page": CreateALandingPage,
- "publish_unpublish_a_landing_page": PublishUnpublishALandingPage,
- "replicate_a_landing_page": ReplicateALandingPage,
- "verify_a_domain": VerifyADomain,
- "connect_a_domain": ConnectADomain,
- "create_customer_journey": CreateCustomerJourney,
- "view_customer_journey": ViewCustomerJourney,
- "edit_customer_journey": EditCustomerJourney,
- "turn_on_pause_turn_back_on": TurnOnPauseTurnBackOn,
- "view_messages": ViewMessages,
- "leave_comments": LeaveComments,
- "send_messages": SendMessages,
- "toggle_user_notifications": ToggleUserNotifications,
- "create_survey": CreateSurvey,
- "edit_survey": EditSurvey,
- "publish_survey": PublishSurvey,
- "delete_survey": DeleteSurvey,
- "create_form": CreateForm,
- "edit_form": EditForm,
- "publish_form": PublishForm,
- "delete_form": DeleteForm,
- }
-
- PermissionIDs = map[Permission]int{
- InviteUsers: 1,
- RevokeAccountAccess: 2,
- SetUserAccessLevel: 3,
- Require2FactorAuthentication: 4,
- ChangeBillingInformation: 5,
- ChangeCompanyOrganizationName: 6,
- AddOrAccessApiKeys: 7,
- CheckReconnectIntegrations: 8,
- ReferralProgram: 9,
- AccountExport: 10,
- CloseAccount: 11,
- AddFilesToContentStudio: 12,
- OptInToReceiveEmailsFromMailchimp: 13,
- CreateAudiences: 14,
- ViewAudiences: 15,
- AudienceExport: 16,
- AudienceImport: 17,
- AddContacts: 18,
- DeleteContacts: 19,
- ViewSegments: 20,
- EditAudienceSettings: 21,
- ArchiveContacts: 22,
- CreateOrImportTemplates: 23,
- EditTemplates: 24,
- CreateEmails: 25,
- EditEmails: 26,
- SendPublishEmails: 27,
- PauseUnpublishEmails: 28,
- DeleteEmails: 29,
- SubmitSmsMarketingApplication: 30,
- CreateSendSmsMmsMessages: 31,
- PurchaseSmsCredits: 32,
- ViewEmailReports: 33,
- ViewSmsReports: 34,
- ViewAbuseReports: 35,
- ViewEmailStatistics: 36,
- UseConversations: 37,
- ViewEmailRecipients: 38,
- TopLocations: 39,
- EmailContactDetails: 40,
- EmailOpenDetails: 41,
- ECommerceProductActivity: 42,
- DomainPerformance: 43,
- CreateYourWebsite: 44,
- PublishUnpublishYourWebsite: 45,
- ViewReport: 46,
- CreateALandingPage: 47,
- PublishUnpublishALandingPage: 48,
- ReplicateALandingPage: 49,
- VerifyADomain: 50,
- ConnectADomain: 51,
- CreateCustomerJourney: 52,
- ViewCustomerJourney: 53,
- EditCustomerJourney: 54,
- TurnOnPauseTurnBackOn: 55,
- ViewMessages: 56,
- LeaveComments: 57,
- SendMessages: 58,
- ToggleUserNotifications: 59,
- CreateSurvey: 60,
- EditSurvey: 61,
- PublishSurvey: 62,
- DeleteSurvey: 63,
- CreateForm: 64,
- EditForm: 65,
- PublishForm: 66,
- DeleteForm: 67,
- }
-
- IdToPermission = map[int]Permission{
- 1: InviteUsers,
- 2: RevokeAccountAccess,
- 3: SetUserAccessLevel,
- 4: Require2FactorAuthentication,
- 5: ChangeBillingInformation,
- 6: ChangeCompanyOrganizationName,
- 7: AddOrAccessApiKeys,
- 8: CheckReconnectIntegrations,
- 9: ReferralProgram,
- 10: AccountExport,
- 11: CloseAccount,
- 12: AddFilesToContentStudio,
- 13: OptInToReceiveEmailsFromMailchimp,
- 14: CreateAudiences,
- 15: ViewAudiences,
- 16: AudienceExport,
- 17: AudienceImport,
- 18: AddContacts,
- 19: DeleteContacts,
- 20: ViewSegments,
- 21: EditAudienceSettings,
- 22: ArchiveContacts,
- 23: CreateOrImportTemplates,
- 24: EditTemplates,
- 25: CreateEmails,
- 26: EditEmails,
- 27: SendPublishEmails,
- 28: PauseUnpublishEmails,
- 29: DeleteEmails,
- 30: SubmitSmsMarketingApplication,
- 31: CreateSendSmsMmsMessages,
- 32: PurchaseSmsCredits,
- 33: ViewEmailReports,
- 34: ViewSmsReports,
- 35: ViewAbuseReports,
- 36: ViewEmailStatistics,
- 37: UseConversations,
- 38: ViewEmailRecipients,
- 39: TopLocations,
- 40: EmailContactDetails,
- 41: EmailOpenDetails,
- 42: ECommerceProductActivity,
- 43: DomainPerformance,
- 44: CreateYourWebsite,
- 45: PublishUnpublishYourWebsite,
- 46: ViewReport,
- 47: CreateALandingPage,
- 48: PublishUnpublishALandingPage,
- 49: ReplicateALandingPage,
- 50: VerifyADomain,
- 51: ConnectADomain,
- 52: CreateCustomerJourney,
- 53: ViewCustomerJourney,
- 54: EditCustomerJourney,
- 55: TurnOnPauseTurnBackOn,
- 56: ViewMessages,
- 57: LeaveComments,
- 58: SendMessages,
- 59: ToggleUserNotifications,
- 60: CreateSurvey,
- 61: EditSurvey,
- 62: PublishSurvey,
- 63: DeleteSurvey,
- 64: CreateForm,
- 65: EditForm,
- 66: PublishForm,
- 67: DeleteForm,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/mailchimp/permissions.yaml b/pkg/analyzer/analyzers/mailchimp/permissions.yaml
deleted file mode 100644
index cc4b921ba7e1..000000000000
--- a/pkg/analyzer/analyzers/mailchimp/permissions.yaml
+++ /dev/null
@@ -1,68 +0,0 @@
-permissions:
- - invite_users
- - revoke_account_access
- - set_user_access_level
- - require_2_factor_authentication
- - change_billing_information
- - change_company_organization_name
- - add_or_access_api_keys
- - check_reconnect_integrations
- - referral_program
- - account_export
- - close_account
- - add_files_to_content_studio
- - opt_in_to_receive_emails_from_mailchimp
- - create_audiences
- - view_audiences
- - audience_export
- - audience_import
- - add_contacts
- - delete_contacts
- - view_segments
- - edit_audience_settings
- - archive_contacts
- - create_or_import_templates
- - edit_templates
- - create_emails
- - edit_emails
- - send_publish_emails
- - pause_unpublish_emails
- - delete_emails
- - submit_sms_marketing_application
- - create_send_sms_mms_messages
- - purchase_sms_credits
- - view_email_reports
- - view_sms_reports
- - view_abuse_reports
- - view_email_statistics
- - use_conversations
- - view_email_recipients
- - top_locations
- - email_contact_details
- - email_open_details
- - e_commerce_product_activity
- - domain_performance
- - create_your_website
- - publish_unpublish_your_website
- - view_report
- - create_a_landing_page
- - publish_unpublish_a_landing_page
- - replicate_a_landing_page
- - verify_a_domain
- - connect_a_domain
- - create_customer_journey
- - view_customer_journey
- - edit_customer_journey
- - turn_on_pause_turn_back_on
- - view_messages
- - leave_comments
- - send_messages
- - toggle_user_notifications
- - create_survey
- - edit_survey
- - publish_survey
- - delete_survey
- - create_form
- - edit_form
- - publish_form
- - delete_form
diff --git a/pkg/analyzer/analyzers/mailgun/expected_output.json b/pkg/analyzer/analyzers/mailgun/expected_output.json
deleted file mode 100644
index 1d1c6f161aed..000000000000
--- a/pkg/analyzer/analyzers/mailgun/expected_output.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "AnalyzerType": 8,
- "Bindings": [
- {
- "Resource": {
- "Name": "sandbox19e49763d44e498e850589ea7d54bd82.mailgun.org",
- "FullyQualifiedName": "mailgun/6478cb31d026c112819856cd/sandbox19e49763d44e498e850589ea7d54bd82.mailgun.org",
- "Type": "domain",
- "Metadata": {
- "created_at": "Thu, 01 Jun 2023 16:45:37 GMT",
- "is_disabled": false,
- "state": "active",
- "type": "sandbox"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": null
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/mailgun/mailgun.go b/pkg/analyzer/analyzers/mailgun/mailgun.go
deleted file mode 100644
index 26dd093ae83d..000000000000
--- a/pkg/analyzer/analyzers/mailgun/mailgun.go
+++ /dev/null
@@ -1,152 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go mailgun
-package mailgun
-
-import (
- "errors"
- "os"
- "strconv"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-type SecretInfo struct {
- ID string // key id
- UserName string
- Type string // type of key
- Role string // key role
- ExpiresAt string // key expiry time if any
- Domains []Domain
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeMailgun }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("key not found in credentialInfo")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeMailgun,
- Bindings: make([]analyzers.Binding, len(info.Domains)),
- }
-
- for idx, domain := range info.Domains {
- result.Bindings[idx] = analyzers.Binding{
- Resource: analyzers.Resource{
- Name: domain.URL,
- FullyQualifiedName: "mailgun/" + domain.ID + "/" + domain.URL,
- Type: "domain",
- Metadata: map[string]any{
- "created_at": domain.CreatedAt,
- "type": domain.Type,
- "state": domain.State,
- "is_disabled": domain.IsDisabled,
- },
- },
-
- Permission: analyzers.Permission{
- Value: PermissionStrings[FullAccess],
- },
- }
- }
- return &result
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, apiKey string) {
- info, err := AnalyzePermissions(cfg, apiKey)
- if err != nil {
- color.Red("[x] %s", err.Error())
- return
- }
-
- color.Green("[i] Valid Mailgun API key\n\n")
- printKeyInfo(info)
- printDomains(info.Domains)
- color.Yellow("[i] Permissions: Full Access\n\n")
-}
-
-func AnalyzePermissions(cfg *config.Config, apiKey string) (*SecretInfo, error) {
- var secretInfo SecretInfo
-
- var client = analyzers.NewAnalyzeClient(cfg)
-
- if err := getDomains(client, apiKey, &secretInfo); err != nil {
- return &secretInfo, err
- }
-
- if err := getKeys(client, apiKey, &secretInfo); err != nil {
- return &secretInfo, err
- }
-
- return &secretInfo, nil
-}
-
-func printKeyInfo(info *SecretInfo) {
- if info.ID == "" {
- color.Red("[i] Key information not found")
- return
- }
-
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Key ID", "UserName/Requester", "Key Type", "Expires At", "Role"})
- t.AppendRow(table.Row{info.ID, info.UserName, info.Type, info.ExpiresAt, info.Role})
- t.Render()
-}
-func printDomains(domains []Domain) {
- if len(domains) == 0 {
- color.Red("[i] No domains found")
- return
- }
-
- color.Yellow("[i] Found %d domain(s)", len(domains))
-
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Domain", "Type", "State", "Created At", "Disabled"})
-
- for _, domain := range domains {
- var colorFunc func(format string, a ...interface{}) string
- switch {
- case domain.IsDisabled:
- colorFunc = color.RedString
- case domain.Type == "sandbox" || domain.State == "unverified":
- colorFunc = color.YellowString
- default:
- colorFunc = color.GreenString
- }
-
- t.AppendRow([]interface{}{
- colorFunc(domain.URL),
- colorFunc(domain.Type),
- colorFunc(domain.State),
- colorFunc(domain.CreatedAt),
- colorFunc(strconv.FormatBool(domain.IsDisabled)),
- })
- }
-
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/mailgun/mailgun_test.go b/pkg/analyzer/analyzers/mailgun/mailgun_test.go
deleted file mode 100644
index faec9e7a260b..000000000000
--- a/pkg/analyzer/analyzers/mailgun/mailgun_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package mailgun
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid Mailgun key",
- key: testSecrets.MustGetField("NEW_MAILGUN_TOKEN_ACTIVE"),
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/mailgun/permissions.go b/pkg/analyzer/analyzers/mailgun/permissions.go
deleted file mode 100644
index 0c4808f057b2..000000000000
--- a/pkg/analyzer/analyzers/mailgun/permissions.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package mailgun
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Read Permission = iota
- Write Permission = iota
- FullAccess Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Read: "read",
- Write: "write",
- FullAccess: "full_access",
- }
-
- StringToPermission = map[string]Permission{
- "read": Read,
- "write": Write,
- "full_access": FullAccess,
- }
-
- PermissionIDs = map[Permission]int{
- Read: 1,
- Write: 2,
- FullAccess: 3,
- }
-
- IdToPermission = map[int]Permission{
- 1: Read,
- 2: Write,
- 3: FullAccess,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/mailgun/permissions.yaml b/pkg/analyzer/analyzers/mailgun/permissions.yaml
deleted file mode 100644
index 7a87b27d1632..000000000000
--- a/pkg/analyzer/analyzers/mailgun/permissions.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-permissions:
- - read
- - write
- - full_access
diff --git a/pkg/analyzer/analyzers/mailgun/requests.go b/pkg/analyzer/analyzers/mailgun/requests.go
deleted file mode 100644
index ab7da22da167..000000000000
--- a/pkg/analyzer/analyzers/mailgun/requests.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package mailgun
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "strings"
-)
-
-// DomainsJSON is /domains API response
-type DomainsJSON struct {
- Items []Domain `json:"items"`
- TotalCount int `json:"total_count"`
-}
-
-// Domain is a single mailgun domain details
-type Domain struct {
- ID string `json:"id"`
- URL string `json:"name"`
- IsDisabled bool `json:"is_disabled"`
- Type string `json:"type"`
- State string `json:"state"`
- CreatedAt string `json:"created_at"`
-}
-
-// KeysJSON is /v1/keys API response
-type KeysJSON struct {
- Items []Key `json:"items"`
- TotalCount int `json:"total_count"`
-}
-
-// Key is a single mailgun Key details
-type Key struct {
- ID string `json:"id"`
- Requester string `json:"requestor"`
- UserName string `json:"user_name"`
- Role string `json:"role"`
- Type string `json:"kind"`
- ExpiresAt string `json:"expires_at"`
-}
-
-// getDomains list all domains
-func getDomains(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
- var domainsJSON DomainsJSON
-
- req, err := http.NewRequest("GET", "https://api.mailgun.net/v4/domains", nil)
- if err != nil {
- return err
- }
-
- req.SetBasicAuth("api", apiKey)
- resp, err := client.Do(req)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != 200 {
- return fmt.Errorf("invalid Mailgun API key")
- }
-
- err = json.NewDecoder(resp.Body).Decode(&domainsJSON)
- if err != nil {
- return err
- }
-
- // populate secretInfo with domains
- secretInfo.Domains = append(secretInfo.Domains, domainsJSON.Items...)
-
- return nil
-}
-
-func getKeys(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
- var keysJSON KeysJSON
-
- req, err := http.NewRequest("GET", "https://api.mailgun.net/v1/keys", nil)
- if err != nil {
- return err
- }
-
- req.SetBasicAuth("api", apiKey)
- resp, err := client.Do(req)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != 200 {
- return fmt.Errorf("invalid Mailgun API key")
- }
-
- err = json.NewDecoder(resp.Body).Decode(&keysJSON)
- if err != nil {
- return err
- }
-
- // populate secretInfo with key details
- for _, key := range keysJSON.Items {
- // filter the exact key which we are analyzing
- // ID is actually the suffix of actual apiKeys
- if strings.Contains(apiKey, key.ID) {
- keyToSecretInfo(key, secretInfo)
- }
- }
-
- return nil
-}
-
-func keyToSecretInfo(key Key, secretInfo *SecretInfo) {
- secretInfo.ID = key.ID
- if key.UserName != "" {
- secretInfo.UserName = key.UserName
- } else {
- secretInfo.UserName = key.Requester
- }
-
- secretInfo.Role = key.Role
- secretInfo.Type = key.Type
- if secretInfo.ExpiresAt != "" {
- secretInfo.ExpiresAt = key.ExpiresAt
- } else {
- secretInfo.ExpiresAt = "Never"
- }
-}
diff --git a/pkg/analyzer/analyzers/monday/monday.go b/pkg/analyzer/analyzers/monday/monday.go
deleted file mode 100644
index f66388132d63..000000000000
--- a/pkg/analyzer/analyzers/monday/monday.go
+++ /dev/null
@@ -1,149 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go monday
-package monday
-
-import (
- "errors"
- "fmt"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-type SecretInfo struct {
- User Me
- Account Account
- Resources []MondayResource
-}
-
-func (s *SecretInfo) appendResource(resource MondayResource) {
- s.Resources = append(s.Resources, resource)
-}
-
-type MondayResource struct {
- ID string
- Name string
- Type string
- MetaData map[string]string
- Parent *MondayResource
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeMonday
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, exist := credInfo["key"]
- if !exist {
- return nil, errors.New("key not found in credentials info")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- // just print the error in cli and continue as a partial success
- color.Red("[x] Error : %s", err.Error())
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- color.Green("[!] Valid Monday Personal Access Token\n\n")
- // print user information
- printUser(info.User)
- printResources(info.Resources)
-
- color.Yellow("\n[i] Expires: Never")
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- // create http client
- client := analyzers.NewAnalyzeClientUnrestricted(cfg)
-
- var secretInfo = &SecretInfo{}
-
- // captureMondayData make a query to graphql API of monday to fetch all data and store it in secret info
- if err := captureMondayData(client, key, secretInfo); err != nil {
- return nil, err
- }
-
- return secretInfo, nil
-}
-
-// secretInfoToAnalyzerResult translate secret info to Analyzer Result
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeMonday,
- Metadata: map[string]any{},
- Bindings: make([]analyzers.Binding, 0),
- }
-
- // extract information from resource to create bindings and append to result bindings
- for _, resource := range info.Resources {
- binding := analyzers.Binding{
- Resource: analyzers.Resource{
- Name: resource.Name,
- FullyQualifiedName: fmt.Sprintf("%s/%s", resource.Type, resource.ID), // e.g: Board/123
- Type: resource.Type,
- Metadata: map[string]any{}, // to avoid panic
- },
- Permission: analyzers.Permission{
- Value: PermissionStrings[FullAccess], // token always has full access
- },
- }
-
- for key, value := range resource.MetaData {
- binding.Resource.Metadata[key] = value
- }
-
- result.Bindings = append(result.Bindings, binding)
-
- }
-
- return &result
-}
-
-// cli print functions
-func printUser(user Me) {
- color.Green("\n[i] User Information:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"ID", "Name", "Email", "Title", "Is Admin", "Is Guest"})
- t.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Name), color.GreenString(user.Email),
- color.GreenString(user.Title), color.GreenString(fmt.Sprintf("%t", user.IsAdmin)), color.GreenString(fmt.Sprintf("%t", user.IsGuest))})
- t.Render()
-}
-
-func printResources(resources []MondayResource) {
- color.Green("\n[i] Resources:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Name", "Type"})
- for _, resource := range resources {
- t.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/monday/monday_test.go b/pkg/analyzer/analyzers/monday/monday_test.go
deleted file mode 100644
index df16590cb57b..000000000000
--- a/pkg/analyzer/analyzers/monday/monday_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package monday
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed result_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "analyzers1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- key := testSecrets.MustGetField("MONDAY_PAT")
-
- tests := []struct {
- name string
- key string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid monday personal access token",
- key: key,
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/monday/permissions.go b/pkg/analyzer/analyzers/monday/permissions.go
deleted file mode 100644
index 8662212b9b60..000000000000
--- a/pkg/analyzer/analyzers/monday/permissions.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package monday
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- FullAccess Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- FullAccess: "full_access",
- }
-
- StringToPermission = map[string]Permission{
- "full_access": FullAccess,
- }
-
- PermissionIDs = map[Permission]int{
- FullAccess: 1,
- }
-
- IdToPermission = map[int]Permission{
- 1: FullAccess,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/monday/permissions.yaml b/pkg/analyzer/analyzers/monday/permissions.yaml
deleted file mode 100644
index 8efa67e1905c..000000000000
--- a/pkg/analyzer/analyzers/monday/permissions.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-permissions:
- - full_access
diff --git a/pkg/analyzer/analyzers/monday/query.go b/pkg/analyzer/analyzers/monday/query.go
deleted file mode 100644
index a0e06ae83bfa..000000000000
--- a/pkg/analyzer/analyzers/monday/query.go
+++ /dev/null
@@ -1,240 +0,0 @@
-package monday
-
-import (
- "bytes"
- _ "embed"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-)
-
-//go:embed query.graphql
-var requestQuery string
-
-const (
- // resource types
- TypeBoard = "Board"
- TypeBoardGroup = "Board Group"
- TypeBoardColumn = "Board Column"
- TypeDoc = "Document"
- TypeFolder = "Folder"
- TypeTag = "Tag"
- TypeTeam = "Team"
- TypeWorkspace = "Workspace"
-)
-
-type Request struct {
- Query string `json:"query"`
-}
-
-// Response is the Monday Graphql API response in case of success
-type Response struct {
- Data Data `json:"data"`
-}
-
-type Data struct {
- Me Me `json:"me"`
- Account Account `json:"account"`
- Users []User `json:"users"`
- Boards []Board `json:"boards"`
- Docs []Doc `json:"docs"`
- Folders []EntityRef `json:"folders"`
- Tags []EntityRef `json:"tags"`
- Teams []EntityRef `json:"teams"`
- Workspaces []Workspace `json:"workspaces"`
-}
-
-type EntityRef struct {
- ID string `json:"id"`
- Name string `json:"name"`
-}
-
-type Me struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Email string `json:"email"`
- Title string `json:"title"`
- IsAdmin bool `json:"is_admin"`
- IsGuest bool `json:"is_guest"`
- IsViewOnly bool `json:"is_view_only"`
- IsPending bool `json:"is_pending"`
- IsVerified bool `json:"is_verified"`
- Teams []EntityRef `json:"teams"`
-}
-
-type Account struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Slug string `json:"slug"`
- Tier string `json:"tier"`
-}
-
-type User struct {
- Email string `json:"email"`
- Account Account `json:"account"`
-}
-
-type Board struct {
- ID string `json:"id"`
- Name string `json:"name"`
- State string `json:"state"`
- Permissions string `json:"permissions"`
- Groups []Group `json:"groups"`
- Columns []Column `json:"column"`
- Owners []EntityRef `json:"owner"`
-}
-
-type Group struct {
- Title string `json:"title"`
- ID string `json:"id"`
-}
-
-type Column struct {
- ID string `json:"id"`
- Title string `json:"title"`
- Type string `json:"type"`
-}
-
-type Doc struct {
- ID string `json:"id"`
- ObjectID string `json:"object_id"`
- Name string `json:"name"`
- CreatedBy EntityRef `json:"created_by"`
-}
-
-type Workspace struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Kind string `json:"kind"`
-}
-
-// captureMondayData send a request to Monday graphql API to get all data and capture it in secret info
-func captureMondayData(client *http.Client, key string, secretInfo *SecretInfo) error {
- jsonData, err := json.Marshal(Request{Query: requestQuery})
- if err != nil {
- panic(err)
- }
-
- req, err := http.NewRequest(http.MethodPost, "https://api.monday.com/v2", bytes.NewBuffer(jsonData))
- if err != nil {
- return err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", key)
- resp, err := client.Do(req)
- if err != nil {
- return err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- var apiResponse Response
-
- if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
- return err
- }
-
- // capture details in secret info
- responseToSecretInfo(apiResponse, secretInfo)
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("expired/invalid access token")
- default:
- return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-// responseToSecretInfo translate api response to secret info
-func responseToSecretInfo(apiResponse Response, secretInfo *SecretInfo) {
- secretInfo.User = apiResponse.Data.Me
- secretInfo.Account = apiResponse.Data.Account
-
- processBoards(apiResponse.Data.Boards, secretInfo)
- processDocs(apiResponse.Data.Docs, secretInfo)
- processSimpleEntities(apiResponse.Data.Folders, TypeFolder, secretInfo)
- processSimpleEntities(apiResponse.Data.Tags, TypeTag, secretInfo)
- processSimpleEntities(apiResponse.Data.Teams, TypeTeam, secretInfo)
- processWorkspaces(apiResponse.Data.Workspaces, secretInfo)
-}
-
-func processBoards(boards []Board, secretInfo *SecretInfo) {
- for _, board := range boards {
- boardResource := MondayResource{
- ID: board.ID,
- Name: board.Name,
- Type: TypeBoard,
- MetaData: map[string]string{
- "state": board.State,
- "permissions": board.Permissions,
- },
- }
-
- secretInfo.appendResource(boardResource)
-
- // sub resources of board
- for _, group := range board.Groups {
- secretInfo.appendResource(MondayResource{
- ID: group.ID,
- Name: group.Title,
- Type: TypeBoardGroup,
- Parent: &boardResource,
- })
- }
-
- for _, column := range board.Columns {
- secretInfo.appendResource(MondayResource{
- ID: column.ID,
- Name: column.Title,
- Type: TypeBoardColumn,
- MetaData: map[string]string{
- "Column Type": column.Type,
- },
- Parent: &boardResource,
- })
- }
- }
-}
-
-func processDocs(docs []Doc, secretInfo *SecretInfo) {
- for _, doc := range docs {
- secretInfo.appendResource(MondayResource{
- ID: doc.ID,
- Name: doc.Name,
- Type: TypeDoc,
- MetaData: map[string]string{
- "created_by": doc.CreatedBy.Name,
- },
- })
- }
-}
-
-func processSimpleEntities(entities []EntityRef, entityType string, secretInfo *SecretInfo) {
- for _, entity := range entities {
- secretInfo.appendResource(MondayResource{
- ID: entity.ID,
- Name: entity.Name,
- Type: entityType,
- })
- }
-}
-
-func processWorkspaces(workspaces []Workspace, secretInfo *SecretInfo) {
- for _, workspace := range workspaces {
- secretInfo.appendResource(MondayResource{
- ID: workspace.ID,
- Name: workspace.Name,
- Type: TypeWorkspace,
- MetaData: map[string]string{
- "workspace_kind": workspace.Kind,
- },
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/monday/query.graphql b/pkg/analyzer/analyzers/monday/query.graphql
deleted file mode 100644
index 8fe74d086eec..000000000000
--- a/pkg/analyzer/analyzers/monday/query.graphql
+++ /dev/null
@@ -1,75 +0,0 @@
-{
- me {
- id
- name
- email
- title
- is_admin
- is_guest
- is_view_only
- is_pending
- is_verified
- teams {
- id
- name
- }
- }
- account {
- id
- name
- slug
- tier
- }
- users {
- email
- account {
- name
- id
- }
- }
- boards {
- id
- name
- state
- permissions
- groups {
- title
- id
- }
- columns {
- id
- title
- type
- }
- owners {
- id
- name
- }
- }
- docs {
- id
- object_id
- name
- created_by {
- id
- name
- }
- }
- folders {
- name
- id
- }
- tags {
- id
- name
- }
- teams {
- id
- name
- }
- workspaces {
- id
- name
- kind
- }
-}
diff --git a/pkg/analyzer/analyzers/monday/result_output.json b/pkg/analyzer/analyzers/monday/result_output.json
deleted file mode 100644
index fadd7b589467..000000000000
--- a/pkg/analyzer/analyzers/monday/result_output.json
+++ /dev/null
@@ -1,276 +0,0 @@
-{
- "AnalyzerType": 35,
- "Bindings": [
- {
- "Resource": {
- "Name": "All Tasks",
- "FullyQualifiedName": "Board Group/topics",
- "Type": "Board Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Bugs Queue",
- "FullyQualifiedName": "Board/2007387485",
- "Type": "Board",
- "Metadata": {
- "permissions": "everyone",
- "state": "active"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Development Work",
- "FullyQualifiedName": "Board Group/new_group24572",
- "Type": "Board Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Epics",
- "FullyQualifiedName": "Board/2007387484",
- "Type": "Board",
- "Metadata": {
- "permissions": "everyone",
- "state": "active"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Epics Backlog",
- "FullyQualifiedName": "Board Group/new_group",
- "Type": "Board Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Getting Started",
- "FullyQualifiedName": "Board/2007387480",
- "Type": "Board",
- "Metadata": {
- "permissions": "everyone",
- "state": "active"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Getting Started",
- "FullyQualifiedName": "Document/1126907",
- "Type": "Document",
- "Metadata": {
- "created_by": "Truffle Security Detectors"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Group Title",
- "FullyQualifiedName": "Board Group/topics",
- "Type": "Board Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Incoming Bugs",
- "FullyQualifiedName": "Board Group/topics",
- "Type": "Board Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Managed in sprints",
- "FullyQualifiedName": "Board Group/new_group",
- "Type": "Board Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My Team",
- "FullyQualifiedName": "Workspace/1857558",
- "Type": "Workspace",
- "Metadata": {
- "workspace_kind": "open"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Q1 2025",
- "FullyQualifiedName": "Board Group/new_group313",
- "Type": "Board Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Resolved",
- "FullyQualifiedName": "Board Group/group_title",
- "Type": "Board Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Retrospectives",
- "FullyQualifiedName": "Board/2007387481",
- "Type": "Board",
- "Metadata": {
- "permissions": "everyone",
- "state": "active"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sprint 1",
- "FullyQualifiedName": "Board Group/group_title",
- "Type": "Board Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sprints",
- "FullyQualifiedName": "Board Group/topics",
- "Type": "Board Group",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sprints",
- "FullyQualifiedName": "Board/2007387482",
- "Type": "Board",
- "Metadata": {
- "permissions": "everyone",
- "state": "active"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sprints order",
- "FullyQualifiedName": "Board/2007387483",
- "Type": "Board",
- "Metadata": {
- "permissions": "everyone",
- "state": "active"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Team OSS team",
- "FullyQualifiedName": "Folder/6205823",
- "Type": "Folder",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {}
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/mux/expected_output.json b/pkg/analyzer/analyzers/mux/expected_output.json
deleted file mode 100644
index e3bdf2af5758..000000000000
--- a/pkg/analyzer/analyzers/mux/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":38,"Bindings":[{"Resource":{"Name":"wV300mH02AsW9XmwfieoMlmLNXConYCREXQHbb7kWAUbw","FullyQualifiedName":"asset/SnmkoMLmA2smTuCHU18H00MOL028LeVQHX3uEQCwD7BMI/track/wV300mH02AsW9XmwfieoMlmLNXConYCREXQHbb7kWAUbw","Type":"track","Metadata":{"duration":16.750067,"languageCode":"","maxChannels":0,"maxFrameRate":29.97,"maxHeight":1080,"maxWidth":1920,"name":"","primary":false,"status":"","textSource":"","textType":"","type":"video"},"Parent":{"Name":"SnmkoMLmA2smTuCHU18H00MOL028LeVQHX3uEQCwD7BMI","FullyQualifiedName":"asset/SnmkoMLmA2smTuCHU18H00MOL028LeVQHX3uEQCwD7BMI","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746529086","duration":16.750067,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"6gYp01Q1DQu02Y1BFChIGYlEReYtMyZWYC601VcrSLK02KA","FullyQualifiedName":"asset/SnmkoMLmA2smTuCHU18H00MOL028LeVQHX3uEQCwD7BMI/playback_id/6gYp01Q1DQu02Y1BFChIGYlEReYtMyZWYC601VcrSLK02KA","Type":"playback_id","Metadata":{"policy":"public"},"Parent":{"Name":"SnmkoMLmA2smTuCHU18H00MOL028LeVQHX3uEQCwD7BMI","FullyQualifiedName":"asset/SnmkoMLmA2smTuCHU18H00MOL028LeVQHX3uEQCwD7BMI","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746529086","duration":16.750067,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"SnmkoMLmA2smTuCHU18H00MOL028LeVQHX3uEQCwD7BMI","FullyQualifiedName":"asset/SnmkoMLmA2smTuCHU18H00MOL028LeVQHX3uEQCwD7BMI","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746529086","duration":16.750067,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"EPWJi6t301mELwzilEDAnZS8T2Uqs8ULDbgZVeCOhLNA","FullyQualifiedName":"asset/a00U5GeBR6pAO3MkdZQBB00enwG1rLwx7UTsYPd0200sylo/track/EPWJi6t301mELwzilEDAnZS8T2Uqs8ULDbgZVeCOhLNA","Type":"track","Metadata":{"duration":16.750067,"languageCode":"","maxChannels":0,"maxFrameRate":29.97,"maxHeight":1080,"maxWidth":1920,"name":"","primary":false,"status":"","textSource":"","textType":"","type":"video"},"Parent":{"Name":"a00U5GeBR6pAO3MkdZQBB00enwG1rLwx7UTsYPd0200sylo","FullyQualifiedName":"asset/a00U5GeBR6pAO3MkdZQBB00enwG1rLwx7UTsYPd0200sylo","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746529083","duration":16.750067,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"X9gY7SmIrDIB5Y02gu8KnUnzuAOi005iOafaJuCQqqZbA","FullyQualifiedName":"asset/a00U5GeBR6pAO3MkdZQBB00enwG1rLwx7UTsYPd0200sylo/playback_id/X9gY7SmIrDIB5Y02gu8KnUnzuAOi005iOafaJuCQqqZbA","Type":"playback_id","Metadata":{"policy":"public"},"Parent":{"Name":"a00U5GeBR6pAO3MkdZQBB00enwG1rLwx7UTsYPd0200sylo","FullyQualifiedName":"asset/a00U5GeBR6pAO3MkdZQBB00enwG1rLwx7UTsYPd0200sylo","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746529083","duration":16.750067,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"a00U5GeBR6pAO3MkdZQBB00enwG1rLwx7UTsYPd0200sylo","FullyQualifiedName":"asset/a00U5GeBR6pAO3MkdZQBB00enwG1rLwx7UTsYPd0200sylo","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746529083","duration":16.750067,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"6nV01pBVrQLad3kjFoEUd023Uxfgl8x8DOK546dqA5xD00","FullyQualifiedName":"asset/QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw/track/6nV01pBVrQLad3kjFoEUd023Uxfgl8x8DOK546dqA5xD00","Type":"track","Metadata":{"duration":25.45,"languageCode":"","maxChannels":2,"maxFrameRate":0,"maxHeight":0,"maxWidth":0,"name":"","primary":true,"status":"","textSource":"","textType":"","type":"audio"},"Parent":{"Name":"QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw","FullyQualifiedName":"asset/QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746513418","duration":25.492133,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"OXOWt16AiGYvtAwuFFfA00hKAHNW02ERja00bvPEWmHLys","FullyQualifiedName":"asset/QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw/track/OXOWt16AiGYvtAwuFFfA00hKAHNW02ERja00bvPEWmHLys","Type":"track","Metadata":{"duration":25.4254,"languageCode":"","maxChannels":0,"maxFrameRate":29.97,"maxHeight":1080,"maxWidth":1920,"name":"","primary":false,"status":"","textSource":"","textType":"","type":"video"},"Parent":{"Name":"QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw","FullyQualifiedName":"asset/QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746513418","duration":25.492133,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"gaJcYKWQ7P01r02XJwU02KAe5KofkQ01497weVghrWNrqlWNYAXHx7fqmQ","FullyQualifiedName":"asset/QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw/track/gaJcYKWQ7P01r02XJwU02KAe5KofkQ01497weVghrWNrqlWNYAXHx7fqmQ","Type":"track","Metadata":{"duration":0,"languageCode":"pt","maxChannels":0,"maxFrameRate":0,"maxHeight":0,"maxWidth":0,"name":"Portuguese","primary":false,"status":"ready","textSource":"generated_vod","textType":"subtitles","type":"text"},"Parent":{"Name":"QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw","FullyQualifiedName":"asset/QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746513418","duration":25.492133,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"hQKQ8U1RkGTN3700ynnDCa00y4q2sCflbKf2Nw01T8OcTc","FullyQualifiedName":"asset/QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw/playback_id/hQKQ8U1RkGTN3700ynnDCa00y4q2sCflbKf2Nw01T8OcTc","Type":"playback_id","Metadata":{"policy":"signed"},"Parent":{"Name":"QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw","FullyQualifiedName":"asset/QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746513418","duration":25.492133,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw","FullyQualifiedName":"asset/QLnXn72rbNKLLvfzVJU2t01atuGSbyYMLsU026jza8YOw","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746513418","duration":25.492133,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"ZJ9HiAaXUO02Da3W7Yj3y5Ct902SIafAbjMTvDhnfaOcs","FullyQualifiedName":"asset/Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8/track/ZJ9HiAaXUO02Da3W7Yj3y5Ct902SIafAbjMTvDhnfaOcs","Type":"track","Metadata":{"duration":25.4254,"languageCode":"","maxChannels":0,"maxFrameRate":29.97,"maxHeight":1080,"maxWidth":1920,"name":"","primary":false,"status":"","textSource":"","textType":"","type":"video"},"Parent":{"Name":"Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8","FullyQualifiedName":"asset/Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746513375","duration":25.492133,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"5Od27uOFUUNPgNhnqwxmc6YQH200q5SD17CRkc25eciM6YMb7c00JvDA","FullyQualifiedName":"asset/Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8/track/5Od27uOFUUNPgNhnqwxmc6YQH200q5SD17CRkc25eciM6YMb7c00JvDA","Type":"track","Metadata":{"duration":0,"languageCode":"it","maxChannels":0,"maxFrameRate":0,"maxHeight":0,"maxWidth":0,"name":"Italian","primary":false,"status":"ready","textSource":"generated_vod","textType":"subtitles","type":"text"},"Parent":{"Name":"Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8","FullyQualifiedName":"asset/Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746513375","duration":25.492133,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"xmgPZVZwnFlWC8Y2Q0046eAOxR88oPP01S5OqHYPLBM01jy601502OoGSwA","FullyQualifiedName":"asset/Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8/track/xmgPZVZwnFlWC8Y2Q0046eAOxR88oPP01S5OqHYPLBM01jy601502OoGSwA","Type":"track","Metadata":{"duration":0,"languageCode":"und","maxChannels":2,"maxFrameRate":0,"maxHeight":0,"maxWidth":0,"name":"Default","primary":true,"status":"ready","textSource":"","textType":"","type":"audio"},"Parent":{"Name":"Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8","FullyQualifiedName":"asset/Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746513375","duration":25.492133,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"GD1K9sPH4Vopr4ticPdOAO02vEIslfN2400cPQnA8YZfo","FullyQualifiedName":"asset/Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8/playback_id/GD1K9sPH4Vopr4ticPdOAO02vEIslfN2400cPQnA8YZfo","Type":"playback_id","Metadata":{"policy":"public"},"Parent":{"Name":"Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8","FullyQualifiedName":"asset/Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746513375","duration":25.492133,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null}},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8","FullyQualifiedName":"asset/Wa5Qmq78b02p2kMCB3P7Lyn4tHjiSkEJT01HEZq8l4Oq8","Type":"asset","Metadata":{"aspectRatio":"16:9","createdAt":"1746513375","duration":25.492133,"mp4Support":"none","status":"ready","videoQuality":"basic"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"8f23e2ab-6780-4dc8-aa8d-8e27339188a6","FullyQualifiedName":"annotation/8f23e2ab-6780-4dc8-aa8d-8e27339188a6","Type":"annotation","Metadata":{"date":"2025-04-23T20:00:00Z","note":"This is a note2","subPropertyID":"123456"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"21d0aca6-9ea4-4d92-a8c5-254a7da2a455","FullyQualifiedName":"annotation/21d0aca6-9ea4-4d92-a8c5-254a7da2a455","Type":"annotation","Metadata":{"date":"2025-04-23T20:00:00Z","note":"This is a note","subPropertyID":"123456"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"NNqGrk9T7Bi4AkaWB38JoOEJiUb417f01P43agSLYXCg","FullyQualifiedName":"signing_key/NNqGrk9T7Bi4AkaWB38JoOEJiUb417f01P43agSLYXCg","Type":"signing_key","Metadata":{"createdAt":"1746615294"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"t4zt5HNbEPmJEDJLpLMXty4d39xMlMgB0292mlbY17sI","FullyQualifiedName":"signing_key/t4zt5HNbEPmJEDJLpLMXty4d39xMlMgB0292mlbY17sI","Type":"signing_key","Metadata":{"createdAt":"1746615296"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"dGQ8BK2joovz4WElQqK02I9ZXN1UeySO27Zn21Y8ATf8","FullyQualifiedName":"signing_key/dGQ8BK2joovz4WElQqK02I9ZXN1UeySO27Zn21Y8ATf8","Type":"signing_key","Metadata":{"createdAt":"1746615298"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"3FA1JvJkG8Ye4i4OpTRcEvFkuNVWkeTLN8BEkKWUko8","FullyQualifiedName":"signing_key/3FA1JvJkG8Ye4i4OpTRcEvFkuNVWkeTLN8BEkKWUko8","Type":"signing_key","Metadata":{"createdAt":"1746615300"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"7kH3cGnX4e5qavqA1Zd7GbxeQ7pDCX702LuXUhCOhdnY","FullyQualifiedName":"signing_key/7kH3cGnX4e5qavqA1Zd7GbxeQ7pDCX702LuXUhCOhdnY","Type":"signing_key","Metadata":{"createdAt":"1746615302"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"T6OmB00xJqpIoM3nbSTRbvHiOjrTGCA7HkKx02UtRRkgk","FullyQualifiedName":"signing_key/T6OmB00xJqpIoM3nbSTRbvHiOjrTGCA7HkKx02UtRRkgk","Type":"signing_key","Metadata":{"createdAt":"1746615304"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"ivN5HCZBsuhWUUBrlusVCB7aseh87N1sAji7XFM8LEs","FullyQualifiedName":"signing_key/ivN5HCZBsuhWUUBrlusVCB7aseh87N1sAji7XFM8LEs","Type":"signing_key","Metadata":{"createdAt":"1746615305"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"jUGFLfsJSqDev6NBjbnOLh4VX6WZJTIuHSFAgomgpkQ","FullyQualifiedName":"signing_key/jUGFLfsJSqDev6NBjbnOLh4VX6WZJTIuHSFAgomgpkQ","Type":"signing_key","Metadata":{"createdAt":"1746615307"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"xnM4650153BqZfmYf167G8Vo91X9Z43im024AwUksPP7o","FullyQualifiedName":"signing_key/xnM4650153BqZfmYf167G8Vo91X9Z43im024AwUksPP7o","Type":"signing_key","Metadata":{"createdAt":"1746615327"},"Parent":null},"Permission":{"Value":"read","Parent":null}}],"UnboundedResources":null,"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/mux/models.go b/pkg/analyzer/analyzers/mux/models.go
deleted file mode 100644
index e7d3b52afa93..000000000000
--- a/pkg/analyzer/analyzers/mux/models.go
+++ /dev/null
@@ -1,142 +0,0 @@
-package mux
-
-import (
- "fmt"
- "net/http"
-)
-
-type ResourceType string
-
-const (
- ResourceTypeVideo ResourceType = "video"
- ResourceTypeData ResourceType = "data"
- ResourceTypeSystem ResourceType = "system"
-)
-
-type permissionTestConfig struct {
- Tests []permissionTest `json:"tests"`
-}
-
-type permissionTest struct {
- ResourceType ResourceType `json:"resource_type"`
- Permission string `json:"permission"`
- Endpoint string `json:"endpoint"`
- Method string `json:"method"`
- ValidStatusCode int `json:"valid_status_code"`
-}
-
-func (test permissionTest) testPermission(client *http.Client, key string, secret string) (bool, error) {
- _, statusCode, err := makeAPIRequest(client, key, secret, test.Method, test.Endpoint)
- if err != nil {
- return false, err
- }
-
- switch statusCode {
- case test.ValidStatusCode:
- return true, nil
- case http.StatusNotFound:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-type secretInfo struct {
- Permissions map[ResourceType]Permission
- Assets []asset
- Annotations []annotation
- SigningKeys []signingKey
-}
-
-func (info *secretInfo) addPermission(resourceType ResourceType, permission string) {
- if info.Permissions == nil {
- info.Permissions = map[ResourceType]Permission{}
- }
- if perm := info.Permissions[resourceType]; perm == FullAccess {
- return
- }
-
- if permission == "read" {
- info.Permissions[resourceType] = Read
- } else if permission == "write" {
- info.Permissions[resourceType] = FullAccess
- }
-}
-
-func (info *secretInfo) hasPermission(resourceType ResourceType, permission Permission) bool {
- perm, exists := info.Permissions[resourceType]
- if !exists {
- return false
- }
- return perm == permission || perm == FullAccess
-}
-
-// Resource structs
-
-type track struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"`
- Duration float64 `json:"duration"`
- Status string `json:"status"`
- Primary bool `json:"primary"`
-
- TextType string `json:"text_type"`
- TextSource string `json:"text_source"`
- LanguageCode string `json:"language_code"`
-
- MaxWidth int `json:"max_width"`
- MaxHeight int `json:"max_height"`
- MaxFrameRate float64 `json:"max_frame_rate"`
- MaxChannels int `json:"max_channels"`
-}
-
-type playbackID struct {
- ID string `json:"id"`
- Policy string `json:"policy"`
-}
-
-type meta struct {
- Title string `json:"title"`
- ExternalID string `json:"external_id"`
- CreatorID string `json:"creator_id"`
-}
-
-type asset struct {
- ID string `json:"id"`
- Duration float64 `json:"duration"`
- Status string `json:"status"`
- VideoQuality string `json:"video_quality"`
- MP4Support string `json:"mp4_support"`
- AspectRatio string `json:"aspect_ratio"`
- Tracks []track `json:"tracks"`
- PlaybackIDs []playbackID `json:"playback_ids"`
- Meta meta `json:"meta"`
- CreatedAt string `json:"created_at"`
-}
-
-type annotation struct {
- SubPropertyID string `json:"sub_property_id"`
- Note string `json:"note"`
- ID string `json:"id"`
- Date string `json:"date"`
-}
-
-type signingKey struct {
- ID string `json:"id"`
- CreatedAt string `json:"created_at"`
-}
-
-// API response structs
-
-type assetListResponse struct {
- Data []asset `json:"data"`
-}
-
-type annotationListResponse struct {
- Data []annotation `json:"data"`
-}
-
-type signingKeyListResponse struct {
- Data []signingKey `json:"data"`
-}
diff --git a/pkg/analyzer/analyzers/mux/mux.go b/pkg/analyzer/analyzers/mux/mux.go
deleted file mode 100644
index b05a9f561fcd..000000000000
--- a/pkg/analyzer/analyzers/mux/mux.go
+++ /dev/null
@@ -1,319 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go mux
-package mux
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- _ "embed"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-//go:embed tests.json
-var testsConfig []byte
-
-func readTestsConfig() (*permissionTestConfig, error) {
- var config permissionTestConfig
- if err := json.Unmarshal(testsConfig, &config); err != nil {
- return nil, fmt.Errorf("failed to unmarshal tests config: %w", err)
- }
- return &config, nil
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeMux
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, exist := credInfo["key"]
- if !exist {
- return nil, errors.New("key not found in credentials info")
- }
- secret, exist := credInfo["secret"]
- if !exist {
- return nil, errors.New("secret not found in credentials info")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key, secret)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string, secret string) {
- info, err := AnalyzePermissions(cfg, key, secret)
- if err != nil {
- color.Red("[x] Invalid Mux Key or Secret\n")
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- color.Green("[i] Valid Mux API Key and Secret\n")
- printResourcesAndPermissions(info)
-}
-
-func AnalyzePermissions(cfg *config.Config, key string, secret string) (*secretInfo, error) {
- client := analyzers.NewAnalyzeClientUnrestricted(cfg)
- secretInfo := &secretInfo{}
- if err := testAllPermissions(client, secretInfo, key, secret); err != nil {
- return nil, err
- }
- if err := populateAllResources(client, secretInfo, key, secret); err != nil {
- return nil, err
- }
-
- return secretInfo, nil
-}
-
-func secretInfoToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- bindings := []analyzers.Binding{}
- readAccessPermission := analyzers.Permission{
- Value: PermissionStrings[Read],
- }
- fullAccessPermission := analyzers.Permission{
- Value: PermissionStrings[FullAccess],
- }
-
- videoResourcePermission := readAccessPermission
- if info.hasPermission(ResourceTypeVideo, FullAccess) {
- videoResourcePermission = fullAccessPermission
- }
-
- dataResourcePermission := readAccessPermission
- if info.hasPermission(ResourceTypeData, FullAccess) {
- dataResourcePermission = fullAccessPermission
- }
-
- systemResourcePermission := readAccessPermission
- if info.hasPermission(ResourceTypeSystem, FullAccess) {
- systemResourcePermission = fullAccessPermission
- }
-
- // Binding all Mux Video Assets
- for _, asset := range info.Assets {
- assetResource := createAssetResource(asset)
- trackResources := createAssetTrackResources(asset, &assetResource)
- playbackIDResources := createAssetPlaybackIDResources(asset, &assetResource)
-
- for _, resource := range trackResources {
- bindings = append(bindings, createBinding(&resource, videoResourcePermission))
- }
- for _, resource := range playbackIDResources {
- bindings = append(bindings, createBinding(&resource, videoResourcePermission))
- }
-
- bindings = append(bindings, createBinding(&assetResource, videoResourcePermission))
- }
-
- // Binding all Mux Data Annotations
- for _, annotation := range info.Annotations {
- annotationResource := createAnnotationResource(annotation)
- bindings = append(bindings, createBinding(&annotationResource, dataResourcePermission))
- }
-
- // Binding all Mux System Signing Keys
- for _, signingKey := range info.SigningKeys {
- signingKeyResource := createSigningKeyResource(signingKey)
- bindings = append(bindings, createBinding(&signingKeyResource, systemResourcePermission))
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeMux,
- Metadata: nil,
- Bindings: bindings,
- }
- return &result
-}
-
-func createBinding(resource *analyzers.Resource, permission analyzers.Permission) analyzers.Binding {
- return analyzers.Binding{
- Resource: *resource,
- Permission: permission,
- }
-}
-
-func printResourcesAndPermissions(info *secretInfo) {
- color.Yellow("\n[i] Permissions:")
- t1 := table.NewWriter()
- t1.AppendHeader(table.Row{"Resource Category", "Access Level", "Resource List"})
-
- for idx, resource := range muxResourcesMap[ResourceTypeVideo] {
- category, access := "", ""
- if idx == 0 {
- category = "Mux Video"
- access = getAccessLevelStringFromPermission(info.Permissions[ResourceTypeVideo])
- }
- t1.AppendRow(table.Row{
- color.GreenString(category),
- color.GreenString(access),
- color.GreenString(resource),
- })
- }
- t1.AppendSeparator()
-
- for idx, resource := range muxResourcesMap[ResourceTypeData] {
- category, access := "", ""
- if idx == 0 {
- category = "Mux Data"
- access = getAccessLevelStringFromPermission(info.Permissions[ResourceTypeData])
- }
- t1.AppendRow(table.Row{
- color.GreenString(category),
- color.GreenString(access),
- color.GreenString(resource),
- })
- }
- t1.AppendSeparator()
-
- for idx, resource := range muxResourcesMap[ResourceTypeSystem] {
- category, access := "", ""
- if idx == 0 {
- category = "Mux System"
- access = getAccessLevelStringFromPermission(info.Permissions[ResourceTypeSystem])
- }
- t1.AppendRow(table.Row{
- color.GreenString(category),
- color.GreenString(access),
- color.GreenString(resource),
- })
- }
- t1.SetOutputMirror(os.Stdout)
- t1.Render()
-
- color.Yellow("\n[i] Resources:")
-
- if info.hasPermission(ResourceTypeVideo, Read) {
- printMuxVideoResources(info)
- }
-
- if info.hasPermission(ResourceTypeData, Read) {
- printMuxDataResources(info)
- }
-
- if info.hasPermission(ResourceTypeSystem, Read) {
- printMuxSystemResources(info)
- }
-
- fmt.Printf("%s: https://www.mux.com/docs/api-reference\n\n", color.GreenString("Ref"))
-}
-
-func printMuxVideoResources(info *secretInfo) {
- t1 := table.NewWriter()
- t1.SetTitle("Assets")
- t1.AppendHeader(table.Row{"ID", "Title", "Duration", "Status", "Creator ID", "External ID", "Created At"})
-
- t2 := table.NewWriter()
- t2.SetTitle("Asset Tracks")
- t2.AppendHeader(table.Row{"Asset ID", "ID", "Name", "Type", "Duration", "Status", "Primary"})
-
- t3 := table.NewWriter()
- t3.SetTitle("Asset Playback IDs")
- t3.AppendHeader(table.Row{"Asset ID", "ID", "Policy"})
-
- for _, asset := range info.Assets {
- t1.AppendRow(table.Row{
- color.GreenString(asset.ID),
- color.GreenString(asset.Meta.Title),
- color.GreenString(fmt.Sprintf("%.2fs", asset.Duration)),
- color.GreenString(asset.Status),
- color.GreenString(asset.Meta.CreatorID),
- color.GreenString(asset.Meta.ExternalID),
- color.GreenString(asset.CreatedAt),
- })
- t1.AppendSeparator()
- for _, track := range asset.Tracks {
- t2.AppendRow(table.Row{
- color.GreenString(asset.ID),
- color.GreenString(track.ID),
- color.GreenString(track.Name),
- color.GreenString(track.Type),
- color.GreenString(fmt.Sprintf("%.2fs", track.Duration)),
- color.GreenString(track.Status),
- color.GreenString(fmt.Sprintf("%t", track.Primary)),
- })
- t2.AppendSeparator()
- }
- for _, playbackID := range asset.PlaybackIDs {
- t3.AppendRow(table.Row{
- color.GreenString(asset.ID),
- color.GreenString(playbackID.ID),
- color.GreenString(playbackID.Policy),
- })
- t3.AppendSeparator()
- }
- }
- t1.SetOutputMirror(os.Stdout)
- t1.Render()
-
- t2.SetOutputMirror(os.Stdout)
- t2.Render()
-
- t3.SetOutputMirror(os.Stdout)
- t3.Render()
-}
-
-func printMuxDataResources(info *secretInfo) {
- t1 := table.NewWriter()
- t1.SetTitle("Annotations")
- t1.AppendHeader(table.Row{"ID", "Note", "Date", "Sub Property ID"})
- for _, annotation := range info.Annotations {
- t1.AppendRow(table.Row{
- color.GreenString(annotation.ID),
- color.GreenString(annotation.Note),
- color.GreenString(annotation.Date),
- color.GreenString(annotation.SubPropertyID),
- })
- }
- t1.SetOutputMirror(os.Stdout)
- t1.Render()
-}
-
-func printMuxSystemResources(info *secretInfo) {
- t1 := table.NewWriter()
- t1.SetTitle("Signing Keys")
- t1.AppendHeader(table.Row{"ID", "Created At"})
- for _, signingKey := range info.SigningKeys {
- t1.AppendRow(table.Row{
- color.GreenString(signingKey.ID),
- color.GreenString(signingKey.CreatedAt),
- })
- }
- t1.SetOutputMirror(os.Stdout)
- t1.Render()
-}
-
-func getAccessLevelStringFromPermission(permission Permission) string {
- switch permission {
- case Read:
- return "Read"
- case FullAccess:
- return "Read & Write"
- default:
- return "None"
- }
-}
diff --git a/pkg/analyzer/analyzers/mux/mux_test.go b/pkg/analyzer/analyzers/mux/mux_test.go
deleted file mode 100644
index fc9d6c3a2c80..000000000000
--- a/pkg/analyzer/analyzers/mux/mux_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package mux
-
-import (
- _ "embed"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "analyzers1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- key := testSecrets.MustGetField("MUX_KEY")
- secret := testSecrets.MustGetField("MUX_SECRET")
-
- tests := []struct {
- name string
- key string
- secret string
- want string
- wantErr bool
- }{
- {
- name: "valid mux credentials",
- key: key,
- secret: secret,
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{
- "key": tt.key,
- "secret": tt.secret,
- })
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // compare the JSON strings
- if string(gotJSON) != string(tt.want) {
- // pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(tt.want, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/mux/permissions.go b/pkg/analyzer/analyzers/mux/permissions.go
deleted file mode 100644
index 05521d2320df..000000000000
--- a/pkg/analyzer/analyzers/mux/permissions.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package mux
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Read Permission = iota
- FullAccess Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Read: "read",
- FullAccess: "full_access",
- }
-
- StringToPermission = map[string]Permission{
- "read": Read,
- "full_access": FullAccess,
- }
-
- PermissionIDs = map[Permission]int{
- Read: 1,
- FullAccess: 2,
- }
-
- IdToPermission = map[int]Permission{
- 1: Read,
- 2: FullAccess,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/mux/permissions.yaml b/pkg/analyzer/analyzers/mux/permissions.yaml
deleted file mode 100644
index b59c28d6ea09..000000000000
--- a/pkg/analyzer/analyzers/mux/permissions.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-permissions:
- - read
- - full_access
diff --git a/pkg/analyzer/analyzers/mux/requests.go b/pkg/analyzer/analyzers/mux/requests.go
deleted file mode 100644
index 533721172613..000000000000
--- a/pkg/analyzer/analyzers/mux/requests.go
+++ /dev/null
@@ -1,148 +0,0 @@
-package mux
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-)
-
-const muxAPIBaseURL = "https://api.mux.com"
-
-func makeAPIRequest(client *http.Client, key, secret, method, endpoint string) ([]byte, int, error) {
- req, err := http.NewRequest(method, muxAPIBaseURL+"/"+endpoint, nil)
- if err != nil {
- return nil, 0, err
- }
-
- req.SetBasicAuth(key, secret)
- res, err := client.Do(req)
- if err != nil {
- return nil, 0, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- body, err := io.ReadAll(res.Body)
- if err != nil {
- return nil, 0, err
- }
-
- return body, res.StatusCode, nil
-}
-
-func testAllPermissions(client *http.Client, info *secretInfo, key string, secret string) error {
- testsConfig, err := readTestsConfig()
- if err != nil {
- return err
- }
-
- for _, test := range testsConfig.Tests {
- hasPermission, err := test.testPermission(client, key, secret)
- if err != nil {
- return err
- }
- if !hasPermission {
- continue
- }
- info.addPermission(test.ResourceType, test.Permission)
- }
-
- return nil
-}
-
-func populateAllResources(client *http.Client, info *secretInfo, key string, secret string) error {
- if info.hasPermission(ResourceTypeVideo, Read) {
- if err := populateAssets(client, info, key, secret); err != nil {
- return err
- }
- }
- if info.hasPermission(ResourceTypeData, Read) {
- if err := populateAnnotations(client, info, key, secret); err != nil {
- return err
- }
- }
- if info.hasPermission(ResourceTypeSystem, Read) {
- if err := populateSigningKeys(client, info, key, secret); err != nil {
- return err
- }
- }
- return nil
-}
-
-func populateAssets(client *http.Client, info *secretInfo, key string, secret string) error {
- const limit = 100
-
- for page := 1; ; page++ {
- url := fmt.Sprintf("/video/v1/assets?limit=%d&page=%d&timeframe[]=100:days", limit, page)
- body, statusCode, err := makeAPIRequest(client, key, secret, http.MethodGet, url)
- if err != nil {
- return err
- }
- if statusCode != http.StatusOK {
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-
- resp := assetListResponse{}
- if err := json.Unmarshal(body, &resp); err != nil {
- return fmt.Errorf("failed to unmarshal data: %w", err)
- }
- if len(resp.Data) == 0 {
- break
- }
- info.Assets = append(info.Assets, resp.Data...)
- }
- return nil
-}
-
-func populateAnnotations(client *http.Client, info *secretInfo, key string, secret string) error {
- const limit = 100
-
- for page := 1; ; page++ {
- url := fmt.Sprintf("/data/v1/annotations?limit=%d&page=%d&timeframe[]=100:days", limit, page)
- body, statusCode, err := makeAPIRequest(client, key, secret, http.MethodGet, url)
- if err != nil {
- return err
- }
- if statusCode != http.StatusOK {
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-
- resp := annotationListResponse{}
- if err := json.Unmarshal(body, &resp); err != nil {
- return fmt.Errorf("failed to unmarshal data: %w", err)
- }
- if len(resp.Data) == 0 {
- break
- }
- info.Annotations = append(info.Annotations, resp.Data...)
- }
- return nil
-}
-
-func populateSigningKeys(client *http.Client, info *secretInfo, key string, secret string) error {
- const limit = 100
-
- for page := 1; ; page++ {
- url := fmt.Sprintf("/system/v1/signing-keys?limit=%d&page=%d&timeframe[]=100:days", limit, page)
- body, statusCode, err := makeAPIRequest(client, key, secret, http.MethodGet, url)
- if err != nil {
- return err
- }
- if statusCode != http.StatusOK {
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-
- resp := signingKeyListResponse{}
- if err := json.Unmarshal(body, &resp); err != nil {
- return fmt.Errorf("failed to unmarshal data: %w", err)
- }
- if len(resp.Data) == 0 {
- break
- }
- info.SigningKeys = append(info.SigningKeys, resp.Data...)
- }
- return nil
-}
diff --git a/pkg/analyzer/analyzers/mux/resources.go b/pkg/analyzer/analyzers/mux/resources.go
deleted file mode 100644
index e0f918d650cb..000000000000
--- a/pkg/analyzer/analyzers/mux/resources.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package mux
-
-import "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
-
-var muxResourcesMap map[ResourceType][]string
-
-func init() {
- muxResourcesMap = map[ResourceType][]string{
- ResourceTypeVideo: {
- "Transcription Vocabularies",
- "Web Inputs",
- "Assets",
- "Live Streams",
- "Uploads",
- "Playback Restrictions",
- "DRM Configurations",
- },
- ResourceTypeData: {
- "Video Views",
- "Filters",
- "Dimensions",
- "Export",
- "Metrics",
- "Monitoring",
- "Realtime",
- "Incidents",
- "Annotations",
- },
- ResourceTypeSystem: {
- "Signing Keys",
- },
- }
-}
-
-func createAssetResource(asset asset) analyzers.Resource {
- return analyzers.Resource{
- Name: asset.ID,
- FullyQualifiedName: "asset/" + asset.ID,
- Type: "asset",
- Metadata: map[string]any{
- "duration": asset.Duration,
- "status": asset.Status,
- "videoQuality": asset.VideoQuality,
- "mp4Support": asset.MP4Support,
- "aspectRatio": asset.AspectRatio,
- "createdAt": asset.CreatedAt,
- },
- }
-}
-
-func createAssetTrackResources(asset asset, parent *analyzers.Resource) []analyzers.Resource {
- trackResources := []analyzers.Resource{}
- for _, track := range asset.Tracks {
- trackResources = append(trackResources, analyzers.Resource{
- Name: track.ID,
- FullyQualifiedName: "asset/" + asset.ID + "/track/" + track.ID,
- Type: "track",
- Metadata: map[string]any{
- "name": track.Name,
- "type": track.Type,
- "duration": track.Duration,
- "status": track.Status,
- "primary": track.Primary,
- "textType": track.TextType,
- "textSource": track.TextSource,
- "languageCode": track.LanguageCode,
- "maxWidth": track.MaxWidth,
- "maxHeight": track.MaxHeight,
- "maxFrameRate": track.MaxFrameRate,
- "maxChannels": track.MaxChannels,
- },
- Parent: parent,
- })
- }
- return trackResources
-}
-
-func createAssetPlaybackIDResources(asset asset, parent *analyzers.Resource) []analyzers.Resource {
- playbackIDResources := []analyzers.Resource{}
- for _, playbackID := range asset.PlaybackIDs {
- playbackIDResources = append(playbackIDResources, analyzers.Resource{
- Name: playbackID.ID,
- FullyQualifiedName: "asset/" + asset.ID + "/playback_id/" + playbackID.ID,
- Type: "playback_id",
- Metadata: map[string]any{
- "policy": playbackID.Policy,
- },
- Parent: parent,
- })
- }
- return playbackIDResources
-}
-
-func createAnnotationResource(annotation annotation) analyzers.Resource {
- return analyzers.Resource{
- Name: annotation.ID,
- FullyQualifiedName: "annotation/" + annotation.ID,
- Type: "annotation",
- Metadata: map[string]any{
- "subPropertyID": annotation.SubPropertyID,
- "note": annotation.Note,
- "date": annotation.Date,
- },
- }
-}
-func createSigningKeyResource(signingKey signingKey) analyzers.Resource {
- return analyzers.Resource{
- Name: signingKey.ID,
- FullyQualifiedName: "signing_key/" + signingKey.ID,
- Type: "signing_key",
- Metadata: map[string]any{
- "createdAt": signingKey.CreatedAt,
- },
- }
-}
diff --git a/pkg/analyzer/analyzers/mux/tests.json b/pkg/analyzer/analyzers/mux/tests.json
deleted file mode 100644
index 9c54f7da5162..000000000000
--- a/pkg/analyzer/analyzers/mux/tests.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "tests": [
- {
- "resource_type": "video",
- "permission": "read",
- "endpoint": "/video/v1/assets?limit=1",
- "method": "GET",
- "valid_status_code": 200
- },
- {
- "resource_type": "video",
- "permission": "write",
- "endpoint": "/video/v1/assets",
- "method": "POST",
- "valid_status_code": 400
- },
- {
- "resource_type": "data",
- "permission": "read",
- "endpoint": "/data/v1/annotations?limit=1",
- "method": "GET",
- "valid_status_code": 200
- },
- {
- "resource_type": "data",
- "permission": "write",
- "endpoint": "/data/v1/annotations",
- "method": "POST",
- "valid_status_code": 400
- },
- {
- "resource_type": "system",
- "permission": "read",
- "endpoint": "/system/v1/signing-keys?limit=1",
- "method": "GET",
- "valid_status_code": 200
- },
- {
- "resource_type": "system",
- "permission": "write",
- "endpoint": "/system/v1/signing-keys",
- "method": "DELETE",
- "valid_status_code": 400
- }
- ]
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/mysql/expected_output.json b/pkg/analyzer/analyzers/mysql/expected_output.json
deleted file mode 100644
index 462b612045e1..000000000000
--- a/pkg/analyzer/analyzers/mysql/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":9,"Bindings":[{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ADMINISTRABLE_ROLE_AUTHORIZATIONS","FullyQualifiedName":"localhost/information_schema/ADMINISTRABLE_ROLE_AUTHORIZATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"APPLICABLE_ROLES","FullyQualifiedName":"localhost/information_schema/APPLICABLE_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"CHARACTER_SETS","FullyQualifiedName":"localhost/information_schema/CHARACTER_SETS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"CHECK_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/CHECK_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"COLLATIONS","FullyQualifiedName":"localhost/information_schema/COLLATIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"COLLATION_CHARACTER_SET_APPLICABILITY","FullyQualifiedName":"localhost/information_schema/COLLATION_CHARACTER_SET_APPLICABILITY","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"COLUMNS","FullyQualifiedName":"localhost/information_schema/COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"COLUMNS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/COLUMNS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"COLUMN_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/COLUMN_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"COLUMN_STATISTICS","FullyQualifiedName":"localhost/information_schema/COLUMN_STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ENABLED_ROLES","FullyQualifiedName":"localhost/information_schema/ENABLED_ROLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ENGINES","FullyQualifiedName":"localhost/information_schema/ENGINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"EVENTS","FullyQualifiedName":"localhost/information_schema/EVENTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"FILES","FullyQualifiedName":"localhost/information_schema/FILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_PAGE_LRU","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_PAGE_LRU","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_BUFFER_POOL_STATS","FullyQualifiedName":"localhost/information_schema/INNODB_BUFFER_POOL_STATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_CACHED_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_CACHED_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_CMP","FullyQualifiedName":"localhost/information_schema/INNODB_CMP","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_CMPMEM_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMPMEM_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_CMP_PER_INDEX_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_PER_INDEX_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_CMP_RESET","FullyQualifiedName":"localhost/information_schema/INNODB_CMP_RESET","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_COLUMNS","FullyQualifiedName":"localhost/information_schema/INNODB_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_DATAFILES","FullyQualifiedName":"localhost/information_schema/INNODB_DATAFILES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_FIELDS","FullyQualifiedName":"localhost/information_schema/INNODB_FIELDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_FOREIGN_COLS","FullyQualifiedName":"localhost/information_schema/INNODB_FOREIGN_COLS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_FT_BEING_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_BEING_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_FT_CONFIG","FullyQualifiedName":"localhost/information_schema/INNODB_FT_CONFIG","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_FT_DEFAULT_STOPWORD","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DEFAULT_STOPWORD","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_FT_DELETED","FullyQualifiedName":"localhost/information_schema/INNODB_FT_DELETED","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_CACHE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_CACHE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_FT_INDEX_TABLE","FullyQualifiedName":"localhost/information_schema/INNODB_FT_INDEX_TABLE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_INDEXES","FullyQualifiedName":"localhost/information_schema/INNODB_INDEXES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_METRICS","FullyQualifiedName":"localhost/information_schema/INNODB_METRICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_SESSION_TEMP_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_SESSION_TEMP_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_TABLES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_TABLESPACES_BRIEF","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESPACES_BRIEF","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_TABLESTATS","FullyQualifiedName":"localhost/information_schema/INNODB_TABLESTATS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_TEMP_TABLE_INFO","FullyQualifiedName":"localhost/information_schema/INNODB_TEMP_TABLE_INFO","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_TRX","FullyQualifiedName":"localhost/information_schema/INNODB_TRX","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"INNODB_VIRTUAL","FullyQualifiedName":"localhost/information_schema/INNODB_VIRTUAL","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"KEYWORDS","FullyQualifiedName":"localhost/information_schema/KEYWORDS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"KEY_COLUMN_USAGE","FullyQualifiedName":"localhost/information_schema/KEY_COLUMN_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"OPTIMIZER_TRACE","FullyQualifiedName":"localhost/information_schema/OPTIMIZER_TRACE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"PARAMETERS","FullyQualifiedName":"localhost/information_schema/PARAMETERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"PARTITIONS","FullyQualifiedName":"localhost/information_schema/PARTITIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"PLUGINS","FullyQualifiedName":"localhost/information_schema/PLUGINS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"PROCESSLIST","FullyQualifiedName":"localhost/information_schema/PROCESSLIST","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"PROFILING","FullyQualifiedName":"localhost/information_schema/PROFILING","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"REFERENTIAL_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/REFERENTIAL_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"RESOURCE_GROUPS","FullyQualifiedName":"localhost/information_schema/RESOURCE_GROUPS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ROLE_COLUMN_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_COLUMN_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ROLE_ROUTINE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_ROUTINE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ROLE_TABLE_GRANTS","FullyQualifiedName":"localhost/information_schema/ROLE_TABLE_GRANTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ROUTINES","FullyQualifiedName":"localhost/information_schema/ROUTINES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"SCHEMATA","FullyQualifiedName":"localhost/information_schema/SCHEMATA","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"SCHEMATA_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/SCHEMATA_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"SCHEMA_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/SCHEMA_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"STATISTICS","FullyQualifiedName":"localhost/information_schema/STATISTICS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ST_GEOMETRY_COLUMNS","FullyQualifiedName":"localhost/information_schema/ST_GEOMETRY_COLUMNS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ST_SPATIAL_REFERENCE_SYSTEMS","FullyQualifiedName":"localhost/information_schema/ST_SPATIAL_REFERENCE_SYSTEMS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ST_UNITS_OF_MEASURE","FullyQualifiedName":"localhost/information_schema/ST_UNITS_OF_MEASURE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"TABLES","FullyQualifiedName":"localhost/information_schema/TABLES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"TABLESPACES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLESPACES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"TABLES_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLES_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"TABLE_CONSTRAINTS_EXTENSIONS","FullyQualifiedName":"localhost/information_schema/TABLE_CONSTRAINTS_EXTENSIONS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"TABLE_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/TABLE_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"TRIGGERS","FullyQualifiedName":"localhost/information_schema/TRIGGERS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"USER_ATTRIBUTES","FullyQualifiedName":"localhost/information_schema/USER_ATTRIBUTES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"USER_PRIVILEGES","FullyQualifiedName":"localhost/information_schema/USER_PRIVILEGES","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"VIEWS","FullyQualifiedName":"localhost/information_schema/VIEWS","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"VIEW_ROUTINE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_ROUTINE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"VIEW_TABLE_USAGE","FullyQualifiedName":"localhost/information_schema/VIEW_TABLE_USAGE","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"information_schema","FullyQualifiedName":"localhost/information_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"accounts","FullyQualifiedName":"localhost/performance_schema/accounts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"binary_log_transaction_compression_stats","FullyQualifiedName":"localhost/performance_schema/binary_log_transaction_compression_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"columns_priv","FullyQualifiedName":"localhost/mysql/columns_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"component","FullyQualifiedName":"localhost/mysql/component","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"cond_instances","FullyQualifiedName":"localhost/performance_schema/cond_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"create_synonym_db","FullyQualifiedName":"localhost/sys/create_synonym_db","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"create_synonym_db","FullyQualifiedName":"localhost/sys/create_synonym_db","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"data_lock_waits","FullyQualifiedName":"localhost/performance_schema/data_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"data_locks","FullyQualifiedName":"localhost/performance_schema/data_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"db","FullyQualifiedName":"localhost/mysql/db","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"default_roles","FullyQualifiedName":"localhost/mysql/default_roles","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"diagnostics","FullyQualifiedName":"localhost/sys/diagnostics","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"diagnostics","FullyQualifiedName":"localhost/sys/diagnostics","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"engine_cost","FullyQualifiedName":"localhost/mysql/engine_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"error_log","FullyQualifiedName":"localhost/performance_schema/error_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_account_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_account_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_host_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_host_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_thread_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_thread_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_errors_summary_by_user_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_by_user_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_errors_summary_global_by_error","FullyQualifiedName":"localhost/performance_schema/events_errors_summary_global_by_error","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_stages_current","FullyQualifiedName":"localhost/performance_schema/events_stages_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_stages_history","FullyQualifiedName":"localhost/performance_schema/events_stages_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_stages_history_long","FullyQualifiedName":"localhost/performance_schema/events_stages_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_stages_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_stages_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_stages_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_current","FullyQualifiedName":"localhost/performance_schema/events_statements_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_histogram_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_histogram_global","FullyQualifiedName":"localhost/performance_schema/events_statements_histogram_global","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_history","FullyQualifiedName":"localhost/performance_schema/events_statements_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_history_long","FullyQualifiedName":"localhost/performance_schema/events_statements_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_digest","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_digest","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_program","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_program","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_statements_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_statements_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_transactions_current","FullyQualifiedName":"localhost/performance_schema/events_transactions_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_transactions_history","FullyQualifiedName":"localhost/performance_schema/events_transactions_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_transactions_history_long","FullyQualifiedName":"localhost/performance_schema/events_transactions_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_transactions_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_transactions_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_transactions_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_waits_current","FullyQualifiedName":"localhost/performance_schema/events_waits_current","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_waits_history","FullyQualifiedName":"localhost/performance_schema/events_waits_history","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_waits_history_long","FullyQualifiedName":"localhost/performance_schema/events_waits_history_long","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_waits_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"events_waits_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/events_waits_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"execute_prepared_stmt","FullyQualifiedName":"localhost/sys/execute_prepared_stmt","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"execute_prepared_stmt","FullyQualifiedName":"localhost/sys/execute_prepared_stmt","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"extract_schema_from_file_name","FullyQualifiedName":"localhost/sys/extract_schema_from_file_name","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"extract_schema_from_file_name","FullyQualifiedName":"localhost/sys/extract_schema_from_file_name","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"extract_table_from_file_name","FullyQualifiedName":"localhost/sys/extract_table_from_file_name","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"extract_table_from_file_name","FullyQualifiedName":"localhost/sys/extract_table_from_file_name","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"file_instances","FullyQualifiedName":"localhost/performance_schema/file_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"file_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/file_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"file_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/file_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"format_bytes","FullyQualifiedName":"localhost/sys/format_bytes","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"format_bytes","FullyQualifiedName":"localhost/sys/format_bytes","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"format_path","FullyQualifiedName":"localhost/sys/format_path","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"format_path","FullyQualifiedName":"localhost/sys/format_path","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"format_statement","FullyQualifiedName":"localhost/sys/format_statement","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"format_statement","FullyQualifiedName":"localhost/sys/format_statement","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"format_time","FullyQualifiedName":"localhost/sys/format_time","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"format_time","FullyQualifiedName":"localhost/sys/format_time","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"func","FullyQualifiedName":"localhost/mysql/func","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"general_log","FullyQualifiedName":"localhost/mysql/general_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"global_grants","FullyQualifiedName":"localhost/mysql/global_grants","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"global_status","FullyQualifiedName":"localhost/performance_schema/global_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"global_variable_attributes","FullyQualifiedName":"localhost/performance_schema/global_variable_attributes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"global_variables","FullyQualifiedName":"localhost/performance_schema/global_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"gtid_executed","FullyQualifiedName":"localhost/mysql/gtid_executed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"help_category","FullyQualifiedName":"localhost/mysql/help_category","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"help_keyword","FullyQualifiedName":"localhost/mysql/help_keyword","Type":"table","Metadata":{"bytes":131072,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"help_relation","FullyQualifiedName":"localhost/mysql/help_relation","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"help_topic","FullyQualifiedName":"localhost/mysql/help_topic","Type":"table","Metadata":{"bytes":1589248,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"host_cache","FullyQualifiedName":"localhost/performance_schema/host_cache","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"host_summary","FullyQualifiedName":"localhost/sys/host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io","FullyQualifiedName":"localhost/sys/host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"host_summary_by_stages","FullyQualifiedName":"localhost/sys/host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"hosts","FullyQualifiedName":"localhost/performance_schema/hosts","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"innodb_index_stats","FullyQualifiedName":"localhost/mysql/innodb_index_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"innodb_lock_waits","FullyQualifiedName":"localhost/sys/innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"innodb_redo_log_files","FullyQualifiedName":"localhost/performance_schema/innodb_redo_log_files","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"innodb_table_stats","FullyQualifiedName":"localhost/mysql/innodb_table_stats","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"keyring_component_status","FullyQualifiedName":"localhost/performance_schema/keyring_component_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"keyring_keys","FullyQualifiedName":"localhost/performance_schema/keyring_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"latest_file_io","FullyQualifiedName":"localhost/sys/latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"list_add","FullyQualifiedName":"localhost/sys/list_add","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"list_add","FullyQualifiedName":"localhost/sys/list_add","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"list_drop","FullyQualifiedName":"localhost/sys/list_drop","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"list_drop","FullyQualifiedName":"localhost/sys/list_drop","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"log_status","FullyQualifiedName":"localhost/performance_schema/log_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"memory_global_total","FullyQualifiedName":"localhost/sys/memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"memory_summary_by_account_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_account_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"memory_summary_by_host_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_host_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"memory_summary_by_thread_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_thread_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"memory_summary_by_user_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_by_user_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"memory_summary_global_by_event_name","FullyQualifiedName":"localhost/performance_schema/memory_summary_global_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"metadata_locks","FullyQualifiedName":"localhost/performance_schema/metadata_locks","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"metrics","FullyQualifiedName":"localhost/sys/metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"mutex_instances","FullyQualifiedName":"localhost/performance_schema/mutex_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"CREATE ROUTINE","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"CREATE TEMPORARY TABLES","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"EVENT","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"LOCK TABLES","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ndb_binlog_index","FullyQualifiedName":"localhost/mysql/ndb_binlog_index","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"objects_summary_global_by_type","FullyQualifiedName":"localhost/performance_schema/objects_summary_global_by_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"password_history","FullyQualifiedName":"localhost/mysql/password_history","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"performance_timers","FullyQualifiedName":"localhost/performance_schema/performance_timers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"persisted_variables","FullyQualifiedName":"localhost/performance_schema/persisted_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"plugin","FullyQualifiedName":"localhost/mysql/plugin","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"prepared_statements_instances","FullyQualifiedName":"localhost/performance_schema/prepared_statements_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/sys/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"processlist","FullyQualifiedName":"localhost/performance_schema/processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"procs_priv","FullyQualifiedName":"localhost/mysql/procs_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"proxies_priv","FullyQualifiedName":"localhost/mysql/proxies_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"ps_check_lost_instrumentation","FullyQualifiedName":"localhost/sys/ps_check_lost_instrumentation","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"ps_is_account_enabled","FullyQualifiedName":"localhost/sys/ps_is_account_enabled","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_is_account_enabled","FullyQualifiedName":"localhost/sys/ps_is_account_enabled","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_is_consumer_enabled","FullyQualifiedName":"localhost/sys/ps_is_consumer_enabled","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_is_consumer_enabled","FullyQualifiedName":"localhost/sys/ps_is_consumer_enabled","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_is_instrument_default_enabled","FullyQualifiedName":"localhost/sys/ps_is_instrument_default_enabled","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_is_instrument_default_enabled","FullyQualifiedName":"localhost/sys/ps_is_instrument_default_enabled","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_is_instrument_default_timed","FullyQualifiedName":"localhost/sys/ps_is_instrument_default_timed","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_is_instrument_default_timed","FullyQualifiedName":"localhost/sys/ps_is_instrument_default_timed","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_is_thread_instrumented","FullyQualifiedName":"localhost/sys/ps_is_thread_instrumented","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_is_thread_instrumented","FullyQualifiedName":"localhost/sys/ps_is_thread_instrumented","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_disable_background_threads","FullyQualifiedName":"localhost/sys/ps_setup_disable_background_threads","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_disable_background_threads","FullyQualifiedName":"localhost/sys/ps_setup_disable_background_threads","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_disable_consumer","FullyQualifiedName":"localhost/sys/ps_setup_disable_consumer","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_disable_consumer","FullyQualifiedName":"localhost/sys/ps_setup_disable_consumer","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_disable_instrument","FullyQualifiedName":"localhost/sys/ps_setup_disable_instrument","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_disable_instrument","FullyQualifiedName":"localhost/sys/ps_setup_disable_instrument","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_disable_thread","FullyQualifiedName":"localhost/sys/ps_setup_disable_thread","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_disable_thread","FullyQualifiedName":"localhost/sys/ps_setup_disable_thread","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_enable_background_threads","FullyQualifiedName":"localhost/sys/ps_setup_enable_background_threads","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_enable_background_threads","FullyQualifiedName":"localhost/sys/ps_setup_enable_background_threads","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_enable_consumer","FullyQualifiedName":"localhost/sys/ps_setup_enable_consumer","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_enable_consumer","FullyQualifiedName":"localhost/sys/ps_setup_enable_consumer","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_enable_instrument","FullyQualifiedName":"localhost/sys/ps_setup_enable_instrument","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_enable_instrument","FullyQualifiedName":"localhost/sys/ps_setup_enable_instrument","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_enable_thread","FullyQualifiedName":"localhost/sys/ps_setup_enable_thread","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_enable_thread","FullyQualifiedName":"localhost/sys/ps_setup_enable_thread","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_reload_saved","FullyQualifiedName":"localhost/sys/ps_setup_reload_saved","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_reload_saved","FullyQualifiedName":"localhost/sys/ps_setup_reload_saved","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_reset_to_default","FullyQualifiedName":"localhost/sys/ps_setup_reset_to_default","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_reset_to_default","FullyQualifiedName":"localhost/sys/ps_setup_reset_to_default","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_save","FullyQualifiedName":"localhost/sys/ps_setup_save","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_save","FullyQualifiedName":"localhost/sys/ps_setup_save","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_show_disabled","FullyQualifiedName":"localhost/sys/ps_setup_show_disabled","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_show_disabled","FullyQualifiedName":"localhost/sys/ps_setup_show_disabled","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_show_disabled_consumers","FullyQualifiedName":"localhost/sys/ps_setup_show_disabled_consumers","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_show_disabled_consumers","FullyQualifiedName":"localhost/sys/ps_setup_show_disabled_consumers","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_show_disabled_instruments","FullyQualifiedName":"localhost/sys/ps_setup_show_disabled_instruments","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_show_disabled_instruments","FullyQualifiedName":"localhost/sys/ps_setup_show_disabled_instruments","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_show_enabled","FullyQualifiedName":"localhost/sys/ps_setup_show_enabled","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_show_enabled","FullyQualifiedName":"localhost/sys/ps_setup_show_enabled","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_show_enabled_consumers","FullyQualifiedName":"localhost/sys/ps_setup_show_enabled_consumers","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_show_enabled_consumers","FullyQualifiedName":"localhost/sys/ps_setup_show_enabled_consumers","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_setup_show_enabled_instruments","FullyQualifiedName":"localhost/sys/ps_setup_show_enabled_instruments","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_setup_show_enabled_instruments","FullyQualifiedName":"localhost/sys/ps_setup_show_enabled_instruments","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_statement_avg_latency_histogram","FullyQualifiedName":"localhost/sys/ps_statement_avg_latency_histogram","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_statement_avg_latency_histogram","FullyQualifiedName":"localhost/sys/ps_statement_avg_latency_histogram","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_thread_account","FullyQualifiedName":"localhost/sys/ps_thread_account","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_thread_account","FullyQualifiedName":"localhost/sys/ps_thread_account","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_thread_id","FullyQualifiedName":"localhost/sys/ps_thread_id","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_thread_id","FullyQualifiedName":"localhost/sys/ps_thread_id","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_thread_stack","FullyQualifiedName":"localhost/sys/ps_thread_stack","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_thread_stack","FullyQualifiedName":"localhost/sys/ps_thread_stack","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_thread_trx_info","FullyQualifiedName":"localhost/sys/ps_thread_trx_info","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_thread_trx_info","FullyQualifiedName":"localhost/sys/ps_thread_trx_info","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_trace_statement_digest","FullyQualifiedName":"localhost/sys/ps_trace_statement_digest","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_trace_statement_digest","FullyQualifiedName":"localhost/sys/ps_trace_statement_digest","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_trace_thread","FullyQualifiedName":"localhost/sys/ps_trace_thread","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_trace_thread","FullyQualifiedName":"localhost/sys/ps_trace_thread","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"ps_truncate_all_tables","FullyQualifiedName":"localhost/sys/ps_truncate_all_tables","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"ps_truncate_all_tables","FullyQualifiedName":"localhost/sys/ps_truncate_all_tables","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"quote_identifier","FullyQualifiedName":"localhost/sys/quote_identifier","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"quote_identifier","FullyQualifiedName":"localhost/sys/quote_identifier","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_applier_configuration","FullyQualifiedName":"localhost/performance_schema/replication_applier_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_applier_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_applier_global_filters","FullyQualifiedName":"localhost/performance_schema/replication_applier_global_filters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_applier_status","FullyQualifiedName":"localhost/performance_schema/replication_applier_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_coordinator","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_coordinator","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_applier_status_by_worker","FullyQualifiedName":"localhost/performance_schema/replication_applier_status_by_worker","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/mysql/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_asynchronous_connection_failover_managed","FullyQualifiedName":"localhost/performance_schema/replication_asynchronous_connection_failover_managed","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_connection_configuration","FullyQualifiedName":"localhost/performance_schema/replication_connection_configuration","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_connection_status","FullyQualifiedName":"localhost/performance_schema/replication_connection_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_group_configuration_version","FullyQualifiedName":"localhost/mysql/replication_group_configuration_version","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_group_member_actions","FullyQualifiedName":"localhost/mysql/replication_group_member_actions","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_group_member_stats","FullyQualifiedName":"localhost/performance_schema/replication_group_member_stats","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"replication_group_members","FullyQualifiedName":"localhost/performance_schema/replication_group_members","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"role_edges","FullyQualifiedName":"localhost/mysql/role_edges","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"ALLOW_NONEXISTENT_DEFINER","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"APPLICATION_PASSWORD_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"AUDIT_ABORT_EXEMPT","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"AUDIT_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"AUTHENTICATION_POLICY_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"BACKUP_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"BINLOG_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"BINLOG_ENCRYPTION_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"CLONE_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"CONNECTION_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"CREATE ROLE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"CREATE ROUTINE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"CREATE TABLESPACE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"CREATE TEMPORARY TABLES","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"CREATE USER","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"DROP ROLE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"ENCRYPTION_KEY_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"EVENT","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"FILE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"FIREWALL_EXEMPT","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"FLUSH_OPTIMIZER_COSTS","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"FLUSH_STATUS","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"FLUSH_TABLES","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"FLUSH_USER_RESOURCES","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"GROUP_REPLICATION_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"GROUP_REPLICATION_STREAM","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"INNODB_REDO_LOG_ARCHIVE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"INNODB_REDO_LOG_ENABLE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"LOCK TABLES","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"PASSWORDLESS_USER_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"PERSIST_RO_VARIABLES_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"PROCESS","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"RELOAD","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"REPLICATION CLIENT","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"REPLICATION SLAVE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"REPLICATION_APPLIER","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"REPLICATION_SLAVE_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"RESOURCE_GROUP_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"RESOURCE_GROUP_USER","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"ROLE_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SENSITIVE_VARIABLES_OBSERVER","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SERVICE_CONNECTION_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SESSION_VARIABLES_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SET_ANY_DEFINER","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SHOW DATABASES","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SHOW_ROUTINE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SHUTDOWN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SUPER","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SYSTEM_USER","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"SYSTEM_VARIABLES_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"TABLE_ENCRYPTION_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"TELEMETRY_LOG_ADMIN","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"TRANSACTION_GTID_TAG","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"XA_RECOVER_ADMIN","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"rwlock_instances","FullyQualifiedName":"localhost/performance_schema/rwlock_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"schema_auto_increment_columns","FullyQualifiedName":"localhost/sys/schema_auto_increment_columns","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"schema_index_statistics","FullyQualifiedName":"localhost/sys/schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"schema_object_overview","FullyQualifiedName":"localhost/sys/schema_object_overview","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"schema_redundant_indexes","FullyQualifiedName":"localhost/sys/schema_redundant_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"schema_table_lock_waits","FullyQualifiedName":"localhost/sys/schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"schema_table_statistics","FullyQualifiedName":"localhost/sys/schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"schema_unused_indexes","FullyQualifiedName":"localhost/sys/schema_unused_indexes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"server_cost","FullyQualifiedName":"localhost/mysql/server_cost","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"servers","FullyQualifiedName":"localhost/mysql/servers","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"session","FullyQualifiedName":"localhost/sys/session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"session_account_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_account_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"session_connect_attrs","FullyQualifiedName":"localhost/performance_schema/session_connect_attrs","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"session_ssl_status","FullyQualifiedName":"localhost/sys/session_ssl_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"session_status","FullyQualifiedName":"localhost/performance_schema/session_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"session_variables","FullyQualifiedName":"localhost/performance_schema/session_variables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"setup_actors","FullyQualifiedName":"localhost/performance_schema/setup_actors","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"setup_consumers","FullyQualifiedName":"localhost/performance_schema/setup_consumers","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"setup_instruments","FullyQualifiedName":"localhost/performance_schema/setup_instruments","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"setup_meters","FullyQualifiedName":"localhost/performance_schema/setup_meters","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"setup_metrics","FullyQualifiedName":"localhost/performance_schema/setup_metrics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"setup_objects","FullyQualifiedName":"localhost/performance_schema/setup_objects","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"setup_threads","FullyQualifiedName":"localhost/performance_schema/setup_threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"slave_master_info","FullyQualifiedName":"localhost/mysql/slave_master_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"slave_relay_log_info","FullyQualifiedName":"localhost/mysql/slave_relay_log_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"slave_worker_info","FullyQualifiedName":"localhost/mysql/slave_worker_info","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"slow_log","FullyQualifiedName":"localhost/mysql/slow_log","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"socket_instances","FullyQualifiedName":"localhost/performance_schema/socket_instances","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"socket_summary_by_event_name","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_event_name","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"socket_summary_by_instance","FullyQualifiedName":"localhost/performance_schema/socket_summary_by_instance","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"statement_analysis","FullyQualifiedName":"localhost/sys/statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"statement_performance_analyzer","FullyQualifiedName":"localhost/sys/statement_performance_analyzer","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"statement_performance_analyzer","FullyQualifiedName":"localhost/sys/statement_performance_analyzer","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"statements_with_sorting","FullyQualifiedName":"localhost/sys/statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"statements_with_temp_tables","FullyQualifiedName":"localhost/sys/statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"status_by_account","FullyQualifiedName":"localhost/performance_schema/status_by_account","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"status_by_host","FullyQualifiedName":"localhost/performance_schema/status_by_host","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"status_by_thread","FullyQualifiedName":"localhost/performance_schema/status_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"status_by_user","FullyQualifiedName":"localhost/performance_schema/status_by_user","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"CREATE ROUTINE","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"CREATE TEMPORARY TABLES","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"EVENT","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"LOCK TABLES","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"sys_config","FullyQualifiedName":"localhost/sys/sys_config","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"sys_get_config","FullyQualifiedName":"localhost/sys/sys_get_config","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"sys_get_config","FullyQualifiedName":"localhost/sys/sys_get_config","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"table_exists","FullyQualifiedName":"localhost/sys/table_exists","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"table_exists","FullyQualifiedName":"localhost/sys/table_exists","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"table_handles","FullyQualifiedName":"localhost/performance_schema/table_handles","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_index_usage","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_index_usage","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"table_io_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_io_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"table_lock_waits_summary_by_table","FullyQualifiedName":"localhost/performance_schema/table_lock_waits_summary_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"tables_priv","FullyQualifiedName":"localhost/mysql/tables_priv","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"threads","FullyQualifiedName":"localhost/performance_schema/threads","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"time_zone","FullyQualifiedName":"localhost/mysql/time_zone","Type":"table","Metadata":{"bytes":81920,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"time_zone_leap_second","FullyQualifiedName":"localhost/mysql/time_zone_leap_second","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"time_zone_name","FullyQualifiedName":"localhost/mysql/time_zone_name","Type":"table","Metadata":{"bytes":245760,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"time_zone_transition","FullyQualifiedName":"localhost/mysql/time_zone_transition","Type":"table","Metadata":{"bytes":4734976,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"time_zone_transition_type","FullyQualifiedName":"localhost/mysql/time_zone_transition_type","Type":"table","Metadata":{"bytes":475136,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"tls_channel_status","FullyQualifiedName":"localhost/performance_schema/tls_channel_status","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"user","FullyQualifiedName":"localhost/mysql/user","Type":"table","Metadata":{"bytes":16384,"non_existent":false},"Parent":{"Name":"mysql","FullyQualifiedName":"localhost/mysql","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"user_defined_functions","FullyQualifiedName":"localhost/performance_schema/user_defined_functions","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"user_summary","FullyQualifiedName":"localhost/sys/user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io","FullyQualifiedName":"localhost/sys/user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"user_summary_by_stages","FullyQualifiedName":"localhost/sys/user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"user_variables_by_thread","FullyQualifiedName":"localhost/performance_schema/user_variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"users","FullyQualifiedName":"localhost/performance_schema/users","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"variables_by_thread","FullyQualifiedName":"localhost/performance_schema/variables_by_thread","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"variables_info","FullyQualifiedName":"localhost/performance_schema/variables_info","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"variables_metadata","FullyQualifiedName":"localhost/performance_schema/variables_metadata","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"performance_schema","FullyQualifiedName":"localhost/performance_schema","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"version","FullyQualifiedName":"localhost/sys/version","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"version_major","FullyQualifiedName":"localhost/sys/version_major","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"version_major","FullyQualifiedName":"localhost/sys/version_major","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"version_minor","FullyQualifiedName":"localhost/sys/version_minor","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"version_minor","FullyQualifiedName":"localhost/sys/version_minor","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"version_patch","FullyQualifiedName":"localhost/sys/version_patch","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER ROUTINE","Parent":null}},{"Resource":{"Name":"version_patch","FullyQualifiedName":"localhost/sys/version_patch","Type":"routine","Metadata":{"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"EXECUTE","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"waits_global_by_latency","FullyQualifiedName":"localhost/sys/waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$host_summary","FullyQualifiedName":"localhost/sys/x$host_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$host_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$host_summary_by_stages","FullyQualifiedName":"localhost/sys/x$host_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$host_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$host_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_schema","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_schema","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$innodb_buffer_stats_by_table","FullyQualifiedName":"localhost/sys/x$innodb_buffer_stats_by_table","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$innodb_lock_waits","FullyQualifiedName":"localhost/sys/x$innodb_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$io_by_thread_by_latency","FullyQualifiedName":"localhost/sys/x$io_by_thread_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$io_global_by_file_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_file_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_bytes","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$io_global_by_wait_by_latency","FullyQualifiedName":"localhost/sys/x$io_global_by_wait_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$latest_file_io","FullyQualifiedName":"localhost/sys/x$latest_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$memory_by_host_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_host_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$memory_by_thread_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_thread_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$memory_by_user_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_by_user_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$memory_global_by_current_bytes","FullyQualifiedName":"localhost/sys/x$memory_global_by_current_bytes","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$memory_global_total","FullyQualifiedName":"localhost/sys/x$memory_global_total","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$processlist","FullyQualifiedName":"localhost/sys/x$processlist","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$ps_digest_95th_percentile_by_avg_us","FullyQualifiedName":"localhost/sys/x$ps_digest_95th_percentile_by_avg_us","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$ps_digest_avg_latency_distribution","FullyQualifiedName":"localhost/sys/x$ps_digest_avg_latency_distribution","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$ps_schema_table_statistics_io","FullyQualifiedName":"localhost/sys/x$ps_schema_table_statistics_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$schema_flattened_keys","FullyQualifiedName":"localhost/sys/x$schema_flattened_keys","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$schema_index_statistics","FullyQualifiedName":"localhost/sys/x$schema_index_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$schema_table_lock_waits","FullyQualifiedName":"localhost/sys/x$schema_table_lock_waits","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics","FullyQualifiedName":"localhost/sys/x$schema_table_statistics","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$schema_table_statistics_with_buffer","FullyQualifiedName":"localhost/sys/x$schema_table_statistics_with_buffer","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$schema_tables_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$schema_tables_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$session","FullyQualifiedName":"localhost/sys/x$session","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$statement_analysis","FullyQualifiedName":"localhost/sys/x$statement_analysis","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$statements_with_errors_or_warnings","FullyQualifiedName":"localhost/sys/x$statements_with_errors_or_warnings","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$statements_with_full_table_scans","FullyQualifiedName":"localhost/sys/x$statements_with_full_table_scans","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$statements_with_runtimes_in_95th_percentile","FullyQualifiedName":"localhost/sys/x$statements_with_runtimes_in_95th_percentile","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$statements_with_sorting","FullyQualifiedName":"localhost/sys/x$statements_with_sorting","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$statements_with_temp_tables","FullyQualifiedName":"localhost/sys/x$statements_with_temp_tables","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$user_summary","FullyQualifiedName":"localhost/sys/x$user_summary","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$user_summary_by_file_io_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_file_io_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$user_summary_by_stages","FullyQualifiedName":"localhost/sys/x$user_summary_by_stages","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_latency","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$user_summary_by_statement_type","FullyQualifiedName":"localhost/sys/x$user_summary_by_statement_type","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_avg_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_avg_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$wait_classes_global_by_latency","FullyQualifiedName":"localhost/sys/x$wait_classes_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$waits_by_host_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_host_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$waits_by_user_by_latency","FullyQualifiedName":"localhost/sys/x$waits_by_user_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"ALTER","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"CREATE VIEW","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DELETE","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"DROP","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INDEX","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"INSERT","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"REFERENCES","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SELECT","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"SHOW VIEW","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"TRIGGER","Parent":null}},{"Resource":{"Name":"x$waits_global_by_latency","FullyQualifiedName":"localhost/sys/x$waits_global_by_latency","Type":"table","Metadata":{"bytes":0,"non_existent":false},"Parent":{"Name":"sys","FullyQualifiedName":"localhost/sys","Type":"database","Metadata":{"default":true,"non_existent":false},"Parent":{"Name":"root@%","FullyQualifiedName":"localhost/root@%","Type":"user","Metadata":null,"Parent":null}}},"Permission":{"Value":"UPDATE","Parent":null}}],"UnboundedResources":null,"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/mysql/mysql.go b/pkg/analyzer/analyzers/mysql/mysql.go
deleted file mode 100644
index 259476122e51..000000000000
--- a/pkg/analyzer/analyzers/mysql/mysql.go
+++ /dev/null
@@ -1,975 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go mysql
-
-package mysql
-
-import (
- "database/sql"
- "fmt"
- "os"
- "strings"
- "time"
-
- "github.com/dustin/go-humanize"
- _ "github.com/go-sql-driver/mysql"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/jedib0t/go-pretty/v6/text"
- "github.com/xo/dburl"
-
- "github.com/fatih/color"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeMySQL }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- uri, ok := credInfo["connection_string"]
- if !ok {
- return nil, fmt.Errorf("missing connection string")
- }
- info, err := AnalyzePermissions(a.Cfg, uri)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeMySQL,
- Metadata: nil,
- Bindings: []analyzers.Binding{},
- }
-
- // add user privileges to bindings
- userBindings, userResource := bakeUserBindings(info)
- result.Bindings = append(result.Bindings, userBindings...)
-
- // add user's database privileges to bindings
- databaseBindings := bakeDatabaseBindings(userResource, info)
- result.Bindings = append(result.Bindings, databaseBindings...)
-
- return &result
-}
-
-func bakeUserBindings(info *SecretInfo) ([]analyzers.Binding, *analyzers.Resource) {
-
- var userBindings []analyzers.Binding
-
- // add user and their privileges to bindings
- userResource := analyzers.Resource{
- Name: info.User,
- FullyQualifiedName: info.Host + "/" + info.User,
- Type: "user",
- }
-
- for _, priv := range info.GlobalPrivs.Privs {
- userBindings = append(userBindings, analyzers.Binding{
- Resource: userResource,
- Permission: analyzers.Permission{
- Value: priv,
- },
- })
- }
-
- return userBindings, &userResource
-}
-
-func bakeDatabaseBindings(userResource *analyzers.Resource, info *SecretInfo) []analyzers.Binding {
- var databaseBindings []analyzers.Binding
-
- for _, database := range info.Databases {
- dbResource := analyzers.Resource{
- Name: database.Name,
- FullyQualifiedName: info.Host + "/" + database.Name,
- Type: "database",
- Metadata: map[string]any{
- "default": database.Default,
- "non_existent": database.Nonexistent,
- },
- Parent: userResource,
- }
-
- for _, priv := range database.Privs {
- databaseBindings = append(databaseBindings, analyzers.Binding{
- Resource: dbResource,
- Permission: analyzers.Permission{
- Value: priv,
- },
- })
- }
-
- // add this database's table privileges to bindings
- tableBindings := bakeTableBindings(&dbResource, database)
- databaseBindings = append(databaseBindings, tableBindings...)
-
- // add this database's routines privileges to bindings
- routineBindings := bakeRoutineBindings(&dbResource, database)
- databaseBindings = append(databaseBindings, routineBindings...)
- }
-
- return databaseBindings
-}
-
-func bakeTableBindings(dbResource *analyzers.Resource, database *Database) []analyzers.Binding {
- if database.Tables == nil {
- return nil
- }
- var tableBindings []analyzers.Binding
- for _, table := range *database.Tables {
- tableResource := analyzers.Resource{
- Name: table.Name,
- FullyQualifiedName: dbResource.FullyQualifiedName + "/" + table.Name,
- Type: "table",
- Metadata: map[string]any{
- "bytes": table.Bytes,
- "non_existent": table.Nonexistent,
- },
- Parent: dbResource,
- }
-
- for _, priv := range table.Privs {
- tableBindings = append(tableBindings, analyzers.Binding{
- Resource: tableResource,
- Permission: analyzers.Permission{
- Value: priv,
- },
- })
- }
-
- // Add this table's column privileges to bindings
- for _, column := range table.Columns {
- columnResource := analyzers.Resource{
- Name: column.Name,
- FullyQualifiedName: tableResource.FullyQualifiedName + "/" + column.Name,
- Type: "column",
- Parent: &tableResource,
- }
-
- for _, priv := range column.Privs {
- tableBindings = append(tableBindings, analyzers.Binding{
- Resource: columnResource,
- Permission: analyzers.Permission{
- Value: priv,
- },
- })
- }
- }
- }
-
- return tableBindings
-}
-
-func bakeRoutineBindings(dbResource *analyzers.Resource, database *Database) []analyzers.Binding {
- if database.Routines == nil {
- return nil
- }
-
- var routineBindings []analyzers.Binding
- for _, routine := range *database.Routines {
- routineResource := analyzers.Resource{
- Name: routine.Name,
- FullyQualifiedName: dbResource.FullyQualifiedName + "/" + routine.Name,
- Type: "routine",
- Metadata: map[string]any{
- "non_existent": routine.Nonexistent,
- },
- Parent: dbResource,
- }
-
- for _, priv := range routine.Privs {
- routineBindings = append(routineBindings, analyzers.Binding{
- Resource: routineResource,
- Permission: analyzers.Permission{
- Value: priv,
- },
- })
- }
- }
-
- return routineBindings
-}
-
-const (
- // MySQL SSL Modes
- mysql_sslmode = "ssl-mode"
- mysql_sslmode_disabled = "DISABLED"
- mysql_sslmode_preferred = "PREFERRED"
- mysql_sslmode_required = "REQUIRED"
- mysql_sslmode_verify_ca = "VERIFY_CA"
- mysql_sslmode_verify_identity = "VERIFY_IDENTITY"
- //https://github.com/go-sql-driver/mysql/issues/899#issuecomment-443493840
-
- // MySQL Built-in Databases
- mysql_db_sys = "sys"
- mysql_db_perf_sch = "performance_schema"
- mysql_db_info_sch = "information_schema"
- mysql_db_mysql = "mysql"
-
- mysql_all = "*"
-)
-
-type GlobalPrivs struct {
- Privs []string
-}
-
-type Database struct {
- Name string
- Default bool
- Tables *[]Table
- Privs []string
- Routines *[]Routine
- Nonexistent bool
-}
-
-type Table struct {
- Name string
- Columns []Column
- Privs []string
- Nonexistent bool
- Bytes uint64
-}
-
-type Column struct {
- Name string
- Privs []string
-}
-
-type Routine struct {
- Name string
- Privs []string
- Nonexistent bool
-}
-
-// so CURRENT_USER returns `doadmin@%` and not `doadmin@localhost
-// USER() returns `doadmin@localhost`
-
-type SecretInfo struct {
- Host string
- User string
- Databases map[string]*Database
- GlobalPrivs GlobalPrivs
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- // ToDo: Add in logging
- if cfg.LoggingEnabled {
- color.Red("[x] Logging is not supported for this analyzer.")
- return
- }
-
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
-
- color.Green("[+] Successfully connected as user: %s", info.User)
-
- // Print the results
- printResults(info.Databases, info.GlobalPrivs, cfg.ShowAll)
-}
-
-func AnalyzePermissions(cfg *config.Config, connectionStr string) (*SecretInfo, error) {
- // Parse the connection string
- u, err := parseConnectionStr(connectionStr)
- if err != nil {
- return nil, fmt.Errorf("parsing the connection string: %w", err)
- }
-
- db, err := createConnection(u)
- if err != nil {
- return nil, fmt.Errorf("connecting to the MySQL database: %w", err)
- }
- defer db.Close()
-
- // Get the current user
- user, err := getUser(db)
- if err != nil {
- return nil, fmt.Errorf("getting the current user: %w", err)
- }
-
- // Get all accessible databases
- var databases = make(map[string]*Database, 0)
- err = getDatabases(db, databases)
- if err != nil {
- return nil, fmt.Errorf("getting databases: %w", err)
- }
- //Get all accessible tables
- err = getTables(db, databases)
- if err != nil {
- return nil, fmt.Errorf("getting tables: %w", err)
- }
- // Get user grants
- grants, err := getGrants(db)
- if err != nil {
- return nil, fmt.Errorf("getting user grants: %w", err)
- }
- // Get all accessible routines
- err = getRoutines(db, databases)
- if err != nil {
- return nil, fmt.Errorf("getting routines: %w", err)
- }
-
- var globalPrivs GlobalPrivs
- // Process user grants
- processGrants(grants, databases, &globalPrivs)
-
- return &SecretInfo{
- Host: u.Hostname(),
- User: user,
- Databases: databases,
- GlobalPrivs: globalPrivs,
- }, nil
-}
-
-func parseConnectionStr(connection string) (*dburl.URL, error) {
- // Check if the connection string starts with 'mysql://'
- if !strings.HasPrefix(connection, "mysql://") {
- color.Yellow("[i] The connection string should start with 'mysql://'. Adding it for you.")
- connection = "mysql://" + connection
- }
-
- // Adapt ssl-mode params to Go MySQL driver
- connection, err := fixTLSQueryParam(connection)
- if err != nil {
- return nil, err
- }
-
- // Parse the connection string
- u, err := dburl.Parse(connection)
- if err != nil {
- return nil, err
- }
- return u, nil
-}
-
-func createConnection(u *dburl.URL) (*sql.DB, error) {
- // Connect to the MySQL database
- db, err := sql.Open("mysql", u.DSN)
- if err != nil {
- return nil, err
- }
-
- db.SetConnMaxLifetime(time.Minute * 5)
- db.SetMaxOpenConns(10)
- db.SetMaxIdleConns(10)
-
- // Check the connection
- err = db.Ping()
- if err != nil {
- if strings.Contains(err.Error(), "certificate signed by unknown authority") {
- return nil, fmt.Errorf("%s. try adding 'ssl-mode=PREFERRED' to your connection string", err.Error())
- }
- return nil, err
- }
-
- return db, nil
-}
-
-func fixTLSQueryParam(connection string) (string, error) {
- // Parse connection string on "?"
- parsed := strings.Split(connection, "?")
-
- // Check if has query parms
- if len(parsed) < 2 {
- // Add 10s timeout
- connection += "?timeout=10s"
- return connection, nil
- }
-
- var error error
-
- // Split parms
- querySlice := strings.Split(parsed[1], "&")
-
- // Check if ssl-mode is present
- for i, part := range querySlice {
- if strings.HasPrefix(part, "ssl-mode") {
- mode := strings.Split(part, "=")[1]
- switch mode {
- case mysql_sslmode_disabled:
- querySlice[i] = "tls=false"
- case mysql_sslmode_preferred:
- querySlice[i] = "tls=preferred"
- case mysql_sslmode_required:
- querySlice[i] = "tls=true"
- case mysql_sslmode_verify_ca:
- error = fmt.Errorf("this implementation does not support VERIFY_CA. try removing it or using ssl-mode=REQUIRED")
- // Need to implement --ssl-ca or --ssl-capath
- case mysql_sslmode_verify_identity:
- error = fmt.Errorf("this implementation does not support VERIFY_IDENTITY. try removing it or using ssl-mode=REQUIRED")
- // Need to implement --ssl-ca or --ssl-capath
- }
- }
- }
-
- // Join the parts back together
- newQuerySlice := strings.Join(querySlice, "&")
- return (parsed[0] + "?" + newQuerySlice + "&timeout=10s"), error
-}
-
-func getUser(db *sql.DB) (string, error) {
- var user string
- err := db.QueryRow("SELECT CURRENT_USER()").Scan(&user)
- if err != nil {
- return "", err
- }
- return user, nil
-}
-
-func getDatabases(db *sql.DB, databases map[string]*Database) error {
- rows, err := db.Query("SHOW DATABASES")
- if err != nil {
- return err
- }
- defer rows.Close()
-
- for rows.Next() {
- var dbName string
- err = rows.Scan(&dbName)
- if err != nil {
- return err
- }
- // check if the database is a built-in database
- built_in_db := false
- switch dbName {
- case mysql_db_sys, mysql_db_perf_sch, mysql_db_info_sch, mysql_db_mysql:
- built_in_db = true
- }
- // add the database to the databases map
- newTables := make([]Table, 0)
- newRoutines := make([]Routine, 0)
- databases[dbName] = &Database{Name: dbName, Default: built_in_db, Tables: &newTables, Routines: &newRoutines}
- }
-
- return nil
-}
-
-func getTables(db *sql.DB, databases map[string]*Database) error {
- rows, err := db.Query("SELECT table_schema, table_name, IFNULL(DATA_LENGTH,0) FROM information_schema.tables")
- if err != nil {
- return err
- }
- defer rows.Close()
-
- for rows.Next() {
- var dbName string
- var tableName string
- var tableSize uint64
- err = rows.Scan(&dbName, &tableName, &tableSize)
- if err != nil {
- return err
- }
-
- // find the database in the databases slice
- d := databases[dbName]
- *d.Tables = append(*d.Tables, Table{Name: tableName, Bytes: tableSize})
- }
-
- return nil
-}
-
-func getRoutines(db *sql.DB, databases map[string]*Database) error {
- rows, err := db.Query("SELECT routine_schema, routine_name FROM information_schema.routines")
- if err != nil {
- return err
- }
- defer rows.Close()
-
- for rows.Next() {
- var dbName string
- var routineName string
- err = rows.Scan(&dbName, &routineName)
- if err != nil {
- return err
- }
- // find the database in the databases slice
- d, ok := databases[dbName]
- if !ok {
- databases[dbName] = &Database{Name: dbName, Default: false, Tables: &[]Table{}, Routines: &[]Routine{}, Nonexistent: true}
- d = databases[dbName]
- }
-
- *d.Routines = append(*d.Routines, Routine{Name: routineName})
- }
-
- return nil
-}
-
-func getGrants(db *sql.DB) ([]string, error) {
- rows, err := db.Query("SHOW GRANTS")
- if err != nil {
- return nil, err
- }
- defer rows.Close()
-
- var grants []string
- for rows.Next() {
- var grant string
- err = rows.Scan(&grant)
- if err != nil {
- return nil, err
- }
- grants = append(grants, grant)
- }
-
- return grants, nil
-}
-
-// ToDo: Deal with these GRANT/REVOKE statements
-// GRANT SELECT (col1), INSERT (col1, col2) ON mydb.mytbl TO 'someuser'@'somehost';
-// GRANT PROXY ON 'localuser'@'localhost' TO 'externaluser'@'somehost';
-// GRANT 'role1', 'role2' TO 'user1'@'localhost', 'user2'@'localhost';
-
-// What are the default privs on information_schema and performance_Schema?
-// Seems table by table...maybe just put "Not Implemented" and leave this to be a show_all option.
-
-// Note: Can't GRANT on a table that doesn't exist, but DB is fine.
-
-// processGrants processes the grants and adds them to the databases structs and globalPrivs
-func processGrants(grants []string, databases map[string]*Database, globalPrivs *GlobalPrivs) {
- for _, grant := range grants {
- // GRANTs on non-existent databases are valid, but we need that object to exist in "databases" for processGrant().
- db := parseDBFromGrant(grant)
- if db == mysql_all {
- continue
- }
- _, ok := databases[db]
- if !ok {
- databases[db] = &Database{Name: db, Default: false, Tables: &[]Table{}, Routines: &[]Routine{}, Nonexistent: true}
- }
- }
- for _, grant := range grants {
- // TODO: How to deal with error here?
- _ = processGrant(grant, databases, globalPrivs)
- }
-}
-
-func processGrant(grant string, databases map[string]*Database, globalPrivs *GlobalPrivs) error {
- isGrant := strings.HasPrefix(grant, "GRANT")
- //hasGrantOption := strings.HasSuffix(grant, "WITH GRANT OPTION")
-
- // remove GRANT or REVOKE
- grant = strings.TrimPrefix(grant, "GRANT")
- grant = strings.TrimPrefix(grant, "REVOKE")
-
- // Split on " ON "
- parts := strings.Split(grant, " ON ")
- if len(parts) < 2 {
- return fmt.Errorf("Error processing grant: %s", grant)
- }
-
- // Put privs in a slice
- privs := strings.Split(parts[0], ",")
- for i, priv := range privs {
- privs[i] = strings.Trim(priv, " ")
- }
-
- // Get DB and Table
- dbName := strings.Trim(strings.Split(parts[1], " TO ")[0], " ")
- if dbName == parts[1] {
- dbName = strings.Trim(strings.Split(parts[1], " FROM ")[0], " ")
- }
-
- // Find the database in the databases slice
- // Note: table may not exist yet OR may be a routine
- dbTableParts := strings.Split(dbName, ".")
- db := strings.Trim(dbTableParts[0], "\"`")
- table := strings.Trim(dbTableParts[1], "\"`")
-
- // dont' forget to deal with revoking db-level privs
-
- if db == mysql_all {
- // Deal with "ALL" and "ALL PRIVILEGES"
- switch privs[0] {
- case "ALL", "ALL PRIVILEGES":
- addRemoveAllPrivs(databases, globalPrivs, isGrant)
- default:
- for _, priv := range privs {
- addRemoveOnePrivOnAll(databases, globalPrivs, priv, isGrant)
- }
- }
- } else {
-
- // Check if the privs are for a routine
- isRoutine := checkIsRoutine(privs)
- if isRoutine {
- db = strings.TrimPrefix(db, "PROCEDURE `")
- db = strings.TrimSuffix(db, "`")
- }
- d := databases[db]
-
- switch {
- case table == mysql_all:
- filteredDBPrivs := filterDBPrivs(privs)
- filteredTablePrivs := filterTablePrivs(privs)
- d.Privs = addRemovePrivs(d.Privs, filteredDBPrivs, isGrant)
- for i, t := range *d.Tables {
- (*d.Tables)[i].Privs = addRemovePrivs(t.Privs, filteredTablePrivs, isGrant)
- }
- case isRoutine:
- var idx = getRoutineIndex(d, table)
- if idx == -1 {
- *d.Routines = append(*d.Routines, Routine{Name: table, Nonexistent: true})
- idx = len(*d.Routines) - 1
- }
- (*d.Routines)[idx].Privs = addRemovePrivs((*d.Routines)[idx].Privs, privs, isGrant)
- default:
- var idx = getTableIndex(d, table)
- if idx == -1 {
- *d.Tables = append(*d.Tables, Table{Name: table, Nonexistent: true, Bytes: 0})
- idx = len(*d.Tables) - 1
- }
- (*d.Tables)[idx].Privs = addRemovePrivs((*d.Tables)[idx].Privs, privs, isGrant)
- }
- }
- return nil
-}
-
-func parseDBFromGrant(grant string) string {
- // Split on " ON "
- parts := strings.Split(grant, " ON ")
- if len(parts) < 2 {
- color.Red("[!] Error processing grant: %s", grant)
- return ""
- }
-
- // Get DB and Table
- dbName := strings.Trim(strings.Split(parts[1], " TO ")[0], " ")
- if dbName == parts[1] {
- dbName = strings.Trim(strings.Split(parts[1], " FROM ")[0], " ")
- }
- dbTableParts := strings.Split(dbName, ".")
- db := strings.Trim(dbTableParts[0], "\"`")
- db = strings.TrimPrefix(db, "PROCEDURE `")
- db = strings.TrimSuffix(db, "`")
- return db
-}
-
-func filterDBPrivs(privs []string) []string {
- filtered := make([]string, 0)
- for _, priv := range privs {
- if SCOPES[priv].Database {
- filtered = append(filtered, priv)
- }
- }
- return filtered
-}
-
-func filterTablePrivs(privs []string) []string {
- filtered := make([]string, 0)
- for _, priv := range privs {
- if SCOPES[priv].Table {
- filtered = append(filtered, priv)
- }
- }
- return filtered
-}
-
-func addRemoveOnePrivOnAll(databases map[string]*Database, globalPrivs *GlobalPrivs, priv string, isGrant bool) {
- scope, ok := SCOPES[priv]
- if !ok {
- color.Red("[!] Error processing grant: privilege doesn't exist in our MySQL (%s)", priv)
- return
- }
-
- slicedPriv := []string{priv}
-
- // Add priv to globalPrivs
- if scope.Global {
- globalPrivs.Privs = addRemovePrivs(globalPrivs.Privs, slicedPriv, isGrant)
- }
-
- // Add/Remove priv to all databases
- if scope.Database {
- for _, d := range databases {
- if d.Name == "information_schema" || d.Name == "performance_schema" {
- continue
- }
- d.Privs = addRemovePrivs(d.Privs, slicedPriv, isGrant)
- }
- }
-
- // Add/Remove priv to all tables
- if scope.Table {
- for _, d := range databases {
- for i, t := range *d.Tables {
- (*d.Tables)[i].Privs = addRemovePrivs(t.Privs, slicedPriv, isGrant)
- }
- }
- }
-
- // Add/Remove priv to all routines
- if scope.Routine {
- for _, d := range databases {
- for i, r := range *d.Routines {
- (*d.Routines)[i].Privs = addRemovePrivs(r.Privs, slicedPriv, isGrant)
- }
- }
- }
-}
-
-func addRemoveAllPrivs(databases map[string]*Database, globalPrivs *GlobalPrivs, isGrant bool) {
- // Add all privs to globalPrivs
- globalAllPrivs := getGlobalAllPrivileges()
- globalPrivs.Privs = addRemovePrivs(globalPrivs.Privs, globalAllPrivs, isGrant)
-
- // Get DB, Table and Routine Privs
- dbAllPrivs := getDBAllPrivs()
- tableAllPrivs := getTableAllPrivs()
- routineAllPrivs := getRoutineAllPrivs()
-
- // Add all privs to all databases and tables and routines
- for _, d := range databases {
- if d.Name == "information_schema" || d.Name == "performance_schema" {
- continue
- }
- // Add DB-level privs
- d.Privs = addRemovePrivs(d.Privs, dbAllPrivs, isGrant)
-
- // Add Table-level privs
- for i, t := range *d.Tables {
- (*d.Tables)[i].Privs = addRemovePrivs(t.Privs, tableAllPrivs, isGrant)
- }
-
- // Add Routine-level privs
- for i, r := range *d.Routines {
- (*d.Routines)[i].Privs = addRemovePrivs(r.Privs, routineAllPrivs, isGrant)
- }
- }
-}
-
-func getGlobalAllPrivileges() []string {
- privs := make([]string, 0)
- for priv, scope := range SCOPES {
- if scope.Global && !scope.Dynamic && priv != "USAGE" && priv != "GRANT OPTION" {
- privs = append(privs, priv)
- }
- }
- return privs
-}
-
-func getDBAllPrivs() []string {
- privs := make([]string, 0)
- for priv, scope := range SCOPES {
- if scope.Database && !scope.Dynamic && priv != "USAGE" && priv != "GRANT OPTION" {
- privs = append(privs, priv)
- }
- }
- return privs
-}
-
-func getTableAllPrivs() []string {
- privs := make([]string, 0)
- for priv, scope := range SCOPES {
- if scope.Table && !scope.Dynamic && priv != "USAGE" && priv != "GRANT OPTION" {
- privs = append(privs, priv)
- }
- }
- return privs
-}
-
-func getRoutineAllPrivs() []string {
- privs := make([]string, 0)
- for priv, scope := range SCOPES {
- if scope.Routine && !scope.Dynamic && priv != "USAGE" && priv != "GRANT OPTION" {
- privs = append(privs, priv)
- }
- }
- return privs
-}
-
-func checkIsRoutine(privs []string) bool {
- if len(privs) > 0 {
- return SCOPES[privs[0]].Routine
- }
- return false
-}
-
-func getTableIndex(d *Database, tableName string) int {
- for i, t := range *d.Tables {
- if t.Name == tableName {
- return i
- }
- }
- return -1
-}
-
-func getRoutineIndex(d *Database, routineName string) int {
- for i, r := range *d.Routines {
- if r.Name == routineName {
- return i
- }
- }
- return -1
-}
-
-func addRemovePrivs(currentPrivs []string, privsToAddRemove []string, add bool) []string {
- newPrivs := make([]string, 0)
- if add {
- newPrivs = append(currentPrivs, privsToAddRemove...)
- return newPrivs
- }
- for _, p := range currentPrivs {
- found := false
- for _, p2 := range privsToAddRemove {
- if p == p2 {
- found = true
- break
- }
- }
- if !found {
- newPrivs = append(newPrivs, p)
- }
- }
- return newPrivs
-}
-
-func printResults(databases map[string]*Database, globalPrivs GlobalPrivs, showAll bool) {
- // Print Global Privileges
- printGlobalPrivs(globalPrivs)
- // Print Database and Table Privileges
- printDBTablePrivs(databases, showAll)
- // Print Routine Privileges
- printRoutinePrivs(databases, showAll)
-}
-
-func printGlobalPrivs(globalPrivs GlobalPrivs) {
- // Prep table writer
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Global Privileges"})
-
- // Print global privs
- globalPrivsStr := ""
- for _, priv := range globalPrivs.Privs {
- globalPrivsStr += priv + ", "
- }
- // Clean up privs string
- globalPrivsStr = cleanPrivStr(globalPrivsStr)
-
- // Add rows of priv string data
- t.AppendRow([]interface{}{analyzers.GreenWriter(text.WrapSoft(globalPrivsStr, 100))})
- t.Render()
-}
-
-func printDBTablePrivs(databases map[string]*Database, showAll bool) {
- // Prep table writer
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Database", "Table", "Privileges", "Est. Size"})
-
- // Print database privs
- for _, d := range databases {
- if isBuiltIn(d.Name) && !showAll {
- continue
- }
-
- // Add privileges to db or table privs strings
- dbPrivsStr := ""
- dbTablesStr := ""
- for _, priv := range d.Privs {
- scope := SCOPES[priv]
- if scope.Database && scope.Table {
- dbTablesStr += priv + ", "
- } else {
- dbPrivsStr += priv + ", "
- }
- }
-
- // Clean up privs strings
- dbPrivsStr = cleanPrivStr(dbPrivsStr)
- dbTablesStr = cleanPrivStr(dbTablesStr)
-
- // Prep String colors
- var dbName string
- var writer func(a ...interface{}) string
- if d.Default {
- dbName = d.Name + " (built-in)"
- writer = analyzers.YellowWriter
- } else if d.Nonexistent {
- dbName = d.Name + " (nonexistent)"
- writer = analyzers.RedWriter
- } else {
- dbName = d.Name
- writer = analyzers.GreenWriter
- }
-
- // Prep Priv Strings
-
- // Add rows of priv string data
- t.AppendRow([]interface{}{writer(dbName), writer(""), writer(text.WrapSoft(dbPrivsStr, 80)), writer("-")})
- t.AppendRow([]interface{}{"", writer(""), writer(text.WrapSoft(dbTablesStr, 80)), writer("-")})
-
- // Print table privs
- for _, t2 := range *d.Tables {
- tablePrivsStr := ""
- for _, priv := range t2.Privs {
- tablePrivsStr += priv + ", "
- }
- tablePrivsStr = cleanPrivStr(tablePrivsStr)
- t.AppendRow([]interface{}{"", writer(t2.Name), writer(text.WrapSoft(tablePrivsStr, 80)), writer(humanize.Bytes(t2.Bytes))})
- }
- // Add a separator between databases
- t.AppendSeparator()
- }
- t.Render()
-}
-
-func printRoutinePrivs(databases map[string]*Database, showAll bool) {
- // Print routine privs
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Database", "Routine", "Privileges"})
-
- // Add rows of priv string data
- for _, d := range databases {
- if isBuiltIn(d.Name) && !showAll {
- continue
- }
- for _, r := range *d.Routines {
- routinePrivsStr := ""
- for _, priv := range r.Privs {
- routinePrivsStr += priv + ", "
- }
- routinePrivsStr = cleanPrivStr(routinePrivsStr)
- var writer func(a ...interface{}) string
- switch d.Name {
- case mysql_db_info_sch, mysql_db_perf_sch, mysql_db_sys, mysql_db_mysql:
- writer = analyzers.YellowWriter
- default:
- writer = analyzers.GreenWriter
- }
- t.AppendRow([]interface{}{writer(d.Name), writer(r.Name), writer(text.WrapSoft(routinePrivsStr, 80))})
- }
- }
- t.Render()
-}
-
-func cleanPrivStr(priv string) string {
- priv = strings.TrimSuffix(priv, ", ")
- if priv == "" {
- priv = "-"
- }
- return priv
-}
-
-func isBuiltIn(dbName string) bool {
- switch dbName {
- case mysql_db_sys, mysql_db_perf_sch, mysql_db_info_sch, mysql_db_mysql:
- return true
- }
- return false
-}
diff --git a/pkg/analyzer/analyzers/mysql/mysql_test.go b/pkg/analyzer/analyzers/mysql/mysql_test.go
deleted file mode 100644
index d31e2c29717b..000000000000
--- a/pkg/analyzer/analyzers/mysql/mysql_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package mysql
-
-import (
- _ "embed"
- "encoding/json"
- "fmt"
- "testing"
-
- "github.com/brianvoe/gofakeit/v7"
- "github.com/google/go-cmp/cmp"
- "github.com/testcontainers/testcontainers-go/modules/mysql"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- mysqlUser := "root"
- mysqlPass := gofakeit.Password(true, true, true, false, false, 10)
- mysqlDatabase := "mysql"
-
- ctx := context.Background()
-
- mysqlC, err := mysql.Run(ctx, "mysql",
- mysql.WithDatabase(mysqlDatabase),
- mysql.WithUsername(mysqlUser),
- mysql.WithPassword(mysqlPass),
- )
- if err != nil {
- t.Fatal(err)
- }
-
- defer func() { _ = mysqlC.Terminate(ctx) }()
-
- host, err := mysqlC.Host(ctx)
- if err != nil {
- t.Fatal(err)
- }
- port, err := mysqlC.MappedPort(ctx, "3306")
- if err != nil {
- t.Fatal(err)
- }
-
- tests := []struct {
- name string
- connectionString string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid Mysql connection",
- connectionString: fmt.Sprintf(`root:%s@%s:%s/%s`, mysqlPass, host, port.Port(), mysqlDatabase),
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(context.Background(), map[string]string{"connection_string": tt.connectionString})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal(tt.want, &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare bindings separately because they are not guaranteed to be in the same order
- if len(got.Bindings) != len(wantObj.Bindings) {
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotJSON, wantJSON)
- return
- }
-
- got.Bindings = nil
- wantObj.Bindings = nil
-
- // Compare the rest of the Object
- if diff := cmp.Diff(&wantObj, got); diff != "" {
- t.Errorf("%s: (-want +got)\n%s", tt.name, diff)
- return
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/mysql/permissions.go b/pkg/analyzer/analyzers/mysql/permissions.go
deleted file mode 100644
index 4b3d13a9372f..000000000000
--- a/pkg/analyzer/analyzers/mysql/permissions.go
+++ /dev/null
@@ -1,451 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package mysql
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Alter Permission = iota
- AlterRoutine Permission = iota
- AllowNonexistentDefiner Permission = iota
- ApplicationPasswordAdmin Permission = iota
- AuditAbortExempt Permission = iota
- AuditAdmin Permission = iota
- AuthenticationPolicyAdmin Permission = iota
- BackupAdmin Permission = iota
- BinlogAdmin Permission = iota
- BinlogEncryptionAdmin Permission = iota
- CloneAdmin Permission = iota
- ConnectionAdmin Permission = iota
- Create Permission = iota
- CreateRole Permission = iota
- CreateRoutine Permission = iota
- CreateTablespace Permission = iota
- CreateTemporaryTables Permission = iota
- CreateUser Permission = iota
- CreateView Permission = iota
- Delete Permission = iota
- Drop Permission = iota
- DropRole Permission = iota
- EncryptionKeyAdmin Permission = iota
- Event Permission = iota
- Execute Permission = iota
- File Permission = iota
- FirewallAdmin Permission = iota
- FirewallExempt Permission = iota
- FirewallUser Permission = iota
- FlushOptimizerCosts Permission = iota
- FlushStatus Permission = iota
- FlushTables Permission = iota
- FlushUserResources Permission = iota
- GrantOption Permission = iota
- GroupReplicationAdmin Permission = iota
- GroupReplicationStream Permission = iota
- Index Permission = iota
- InnodbRedoLogArchive Permission = iota
- InnodbRedoLogEnable Permission = iota
- Insert Permission = iota
- LockingTables Permission = iota
- MaskingDictionariesAdmin Permission = iota
- NdbStoredUser Permission = iota
- PasswordlessUserAdmin Permission = iota
- PersistRoVariablesAdmin Permission = iota
- Process Permission = iota
- Proxy Permission = iota
- References Permission = iota
- Reload Permission = iota
- ReplicationApplier Permission = iota
- ReplicationClient Permission = iota
- ReplicationSlave Permission = iota
- ReplicationSlaveAdmin Permission = iota
- ResourceGroupAdmin Permission = iota
- ResourceGroupUser Permission = iota
- RoleAdmin Permission = iota
- Select Permission = iota
- SensitiveVariablesObserver Permission = iota
- ServiceConnectionAdmin Permission = iota
- SessionVariablesAdmin Permission = iota
- SetAnyDefiner Permission = iota
- SetUserId Permission = iota
- ShowDatabases Permission = iota
- ShowRoutine Permission = iota
- ShowView Permission = iota
- Shutdown Permission = iota
- SkipQueryRewrite Permission = iota
- Super Permission = iota
- SystemUser Permission = iota
- SystemVariablesAdmin Permission = iota
- TableEncryptionAdmin Permission = iota
- TelemetryLogAdmin Permission = iota
- TpConnectionAdmin Permission = iota
- TransactionGtidTag Permission = iota
- Trigger Permission = iota
- Update Permission = iota
- Usage Permission = iota
- VersionTokenAdmin Permission = iota
- XaRecoverAdmin Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Alter: "alter",
- AlterRoutine: "alter_routine",
- AllowNonexistentDefiner: "allow_nonexistent_definer",
- ApplicationPasswordAdmin: "application_password_admin",
- AuditAbortExempt: "audit_abort_exempt",
- AuditAdmin: "audit_admin",
- AuthenticationPolicyAdmin: "authentication_policy_admin",
- BackupAdmin: "backup_admin",
- BinlogAdmin: "binlog_admin",
- BinlogEncryptionAdmin: "binlog_encryption_admin",
- CloneAdmin: "clone_admin",
- ConnectionAdmin: "connection_admin",
- Create: "create",
- CreateRole: "create_role",
- CreateRoutine: "create_routine",
- CreateTablespace: "create_tablespace",
- CreateTemporaryTables: "create_temporary_tables",
- CreateUser: "create_user",
- CreateView: "create_view",
- Delete: "delete",
- Drop: "drop",
- DropRole: "drop_role",
- EncryptionKeyAdmin: "encryption_key_admin",
- Event: "event",
- Execute: "execute",
- File: "file",
- FirewallAdmin: "firewall_admin",
- FirewallExempt: "firewall_exempt",
- FirewallUser: "firewall_user",
- FlushOptimizerCosts: "flush_optimizer_costs",
- FlushStatus: "flush_status",
- FlushTables: "flush_tables",
- FlushUserResources: "flush_user_resources",
- GrantOption: "grant_option",
- GroupReplicationAdmin: "group_replication_admin",
- GroupReplicationStream: "group_replication_stream",
- Index: "index",
- InnodbRedoLogArchive: "innodb_redo_log_archive",
- InnodbRedoLogEnable: "innodb_redo_log_enable",
- Insert: "insert",
- LockingTables: "locking_tables",
- MaskingDictionariesAdmin: "masking_dictionaries_admin",
- NdbStoredUser: "ndb_stored_user",
- PasswordlessUserAdmin: "passwordless_user_admin",
- PersistRoVariablesAdmin: "persist_ro_variables_admin",
- Process: "process",
- Proxy: "proxy",
- References: "references",
- Reload: "reload",
- ReplicationApplier: "replication_applier",
- ReplicationClient: "replication_client",
- ReplicationSlave: "replication_slave",
- ReplicationSlaveAdmin: "replication_slave_admin",
- ResourceGroupAdmin: "resource_group_admin",
- ResourceGroupUser: "resource_group_user",
- RoleAdmin: "role_admin",
- Select: "select",
- SensitiveVariablesObserver: "sensitive_variables_observer",
- ServiceConnectionAdmin: "service_connection_admin",
- SessionVariablesAdmin: "session_variables_admin",
- SetAnyDefiner: "set_any_definer",
- SetUserId: "set_user_id",
- ShowDatabases: "show_databases",
- ShowRoutine: "show_routine",
- ShowView: "show_view",
- Shutdown: "shutdown",
- SkipQueryRewrite: "skip_query_rewrite",
- Super: "super",
- SystemUser: "system_user",
- SystemVariablesAdmin: "system_variables_admin",
- TableEncryptionAdmin: "table_encryption_admin",
- TelemetryLogAdmin: "telemetry_log_admin",
- TpConnectionAdmin: "tp_connection_admin",
- TransactionGtidTag: "transaction_gtid_tag",
- Trigger: "trigger",
- Update: "update",
- Usage: "usage",
- VersionTokenAdmin: "version_token_admin",
- XaRecoverAdmin: "xa_recover_admin",
- }
-
- StringToPermission = map[string]Permission{
- "alter": Alter,
- "alter_routine": AlterRoutine,
- "allow_nonexistent_definer": AllowNonexistentDefiner,
- "application_password_admin": ApplicationPasswordAdmin,
- "audit_abort_exempt": AuditAbortExempt,
- "audit_admin": AuditAdmin,
- "authentication_policy_admin": AuthenticationPolicyAdmin,
- "backup_admin": BackupAdmin,
- "binlog_admin": BinlogAdmin,
- "binlog_encryption_admin": BinlogEncryptionAdmin,
- "clone_admin": CloneAdmin,
- "connection_admin": ConnectionAdmin,
- "create": Create,
- "create_role": CreateRole,
- "create_routine": CreateRoutine,
- "create_tablespace": CreateTablespace,
- "create_temporary_tables": CreateTemporaryTables,
- "create_user": CreateUser,
- "create_view": CreateView,
- "delete": Delete,
- "drop": Drop,
- "drop_role": DropRole,
- "encryption_key_admin": EncryptionKeyAdmin,
- "event": Event,
- "execute": Execute,
- "file": File,
- "firewall_admin": FirewallAdmin,
- "firewall_exempt": FirewallExempt,
- "firewall_user": FirewallUser,
- "flush_optimizer_costs": FlushOptimizerCosts,
- "flush_status": FlushStatus,
- "flush_tables": FlushTables,
- "flush_user_resources": FlushUserResources,
- "grant_option": GrantOption,
- "group_replication_admin": GroupReplicationAdmin,
- "group_replication_stream": GroupReplicationStream,
- "index": Index,
- "innodb_redo_log_archive": InnodbRedoLogArchive,
- "innodb_redo_log_enable": InnodbRedoLogEnable,
- "insert": Insert,
- "locking_tables": LockingTables,
- "masking_dictionaries_admin": MaskingDictionariesAdmin,
- "ndb_stored_user": NdbStoredUser,
- "passwordless_user_admin": PasswordlessUserAdmin,
- "persist_ro_variables_admin": PersistRoVariablesAdmin,
- "process": Process,
- "proxy": Proxy,
- "references": References,
- "reload": Reload,
- "replication_applier": ReplicationApplier,
- "replication_client": ReplicationClient,
- "replication_slave": ReplicationSlave,
- "replication_slave_admin": ReplicationSlaveAdmin,
- "resource_group_admin": ResourceGroupAdmin,
- "resource_group_user": ResourceGroupUser,
- "role_admin": RoleAdmin,
- "select": Select,
- "sensitive_variables_observer": SensitiveVariablesObserver,
- "service_connection_admin": ServiceConnectionAdmin,
- "session_variables_admin": SessionVariablesAdmin,
- "set_any_definer": SetAnyDefiner,
- "set_user_id": SetUserId,
- "show_databases": ShowDatabases,
- "show_routine": ShowRoutine,
- "show_view": ShowView,
- "shutdown": Shutdown,
- "skip_query_rewrite": SkipQueryRewrite,
- "super": Super,
- "system_user": SystemUser,
- "system_variables_admin": SystemVariablesAdmin,
- "table_encryption_admin": TableEncryptionAdmin,
- "telemetry_log_admin": TelemetryLogAdmin,
- "tp_connection_admin": TpConnectionAdmin,
- "transaction_gtid_tag": TransactionGtidTag,
- "trigger": Trigger,
- "update": Update,
- "usage": Usage,
- "version_token_admin": VersionTokenAdmin,
- "xa_recover_admin": XaRecoverAdmin,
- }
-
- PermissionIDs = map[Permission]int{
- Alter: 1,
- AlterRoutine: 2,
- AllowNonexistentDefiner: 3,
- ApplicationPasswordAdmin: 4,
- AuditAbortExempt: 5,
- AuditAdmin: 6,
- AuthenticationPolicyAdmin: 7,
- BackupAdmin: 8,
- BinlogAdmin: 9,
- BinlogEncryptionAdmin: 10,
- CloneAdmin: 11,
- ConnectionAdmin: 12,
- Create: 13,
- CreateRole: 14,
- CreateRoutine: 15,
- CreateTablespace: 16,
- CreateTemporaryTables: 17,
- CreateUser: 18,
- CreateView: 19,
- Delete: 20,
- Drop: 21,
- DropRole: 22,
- EncryptionKeyAdmin: 23,
- Event: 24,
- Execute: 25,
- File: 26,
- FirewallAdmin: 27,
- FirewallExempt: 28,
- FirewallUser: 29,
- FlushOptimizerCosts: 30,
- FlushStatus: 31,
- FlushTables: 32,
- FlushUserResources: 33,
- GrantOption: 34,
- GroupReplicationAdmin: 35,
- GroupReplicationStream: 36,
- Index: 37,
- InnodbRedoLogArchive: 38,
- InnodbRedoLogEnable: 39,
- Insert: 40,
- LockingTables: 41,
- MaskingDictionariesAdmin: 42,
- NdbStoredUser: 43,
- PasswordlessUserAdmin: 44,
- PersistRoVariablesAdmin: 45,
- Process: 46,
- Proxy: 47,
- References: 48,
- Reload: 49,
- ReplicationApplier: 50,
- ReplicationClient: 51,
- ReplicationSlave: 52,
- ReplicationSlaveAdmin: 53,
- ResourceGroupAdmin: 54,
- ResourceGroupUser: 55,
- RoleAdmin: 56,
- Select: 57,
- SensitiveVariablesObserver: 58,
- ServiceConnectionAdmin: 59,
- SessionVariablesAdmin: 60,
- SetAnyDefiner: 61,
- SetUserId: 62,
- ShowDatabases: 63,
- ShowRoutine: 64,
- ShowView: 65,
- Shutdown: 66,
- SkipQueryRewrite: 67,
- Super: 68,
- SystemUser: 69,
- SystemVariablesAdmin: 70,
- TableEncryptionAdmin: 71,
- TelemetryLogAdmin: 72,
- TpConnectionAdmin: 73,
- TransactionGtidTag: 74,
- Trigger: 75,
- Update: 76,
- Usage: 77,
- VersionTokenAdmin: 78,
- XaRecoverAdmin: 79,
- }
-
- IdToPermission = map[int]Permission{
- 1: Alter,
- 2: AlterRoutine,
- 3: AllowNonexistentDefiner,
- 4: ApplicationPasswordAdmin,
- 5: AuditAbortExempt,
- 6: AuditAdmin,
- 7: AuthenticationPolicyAdmin,
- 8: BackupAdmin,
- 9: BinlogAdmin,
- 10: BinlogEncryptionAdmin,
- 11: CloneAdmin,
- 12: ConnectionAdmin,
- 13: Create,
- 14: CreateRole,
- 15: CreateRoutine,
- 16: CreateTablespace,
- 17: CreateTemporaryTables,
- 18: CreateUser,
- 19: CreateView,
- 20: Delete,
- 21: Drop,
- 22: DropRole,
- 23: EncryptionKeyAdmin,
- 24: Event,
- 25: Execute,
- 26: File,
- 27: FirewallAdmin,
- 28: FirewallExempt,
- 29: FirewallUser,
- 30: FlushOptimizerCosts,
- 31: FlushStatus,
- 32: FlushTables,
- 33: FlushUserResources,
- 34: GrantOption,
- 35: GroupReplicationAdmin,
- 36: GroupReplicationStream,
- 37: Index,
- 38: InnodbRedoLogArchive,
- 39: InnodbRedoLogEnable,
- 40: Insert,
- 41: LockingTables,
- 42: MaskingDictionariesAdmin,
- 43: NdbStoredUser,
- 44: PasswordlessUserAdmin,
- 45: PersistRoVariablesAdmin,
- 46: Process,
- 47: Proxy,
- 48: References,
- 49: Reload,
- 50: ReplicationApplier,
- 51: ReplicationClient,
- 52: ReplicationSlave,
- 53: ReplicationSlaveAdmin,
- 54: ResourceGroupAdmin,
- 55: ResourceGroupUser,
- 56: RoleAdmin,
- 57: Select,
- 58: SensitiveVariablesObserver,
- 59: ServiceConnectionAdmin,
- 60: SessionVariablesAdmin,
- 61: SetAnyDefiner,
- 62: SetUserId,
- 63: ShowDatabases,
- 64: ShowRoutine,
- 65: ShowView,
- 66: Shutdown,
- 67: SkipQueryRewrite,
- 68: Super,
- 69: SystemUser,
- 70: SystemVariablesAdmin,
- 71: TableEncryptionAdmin,
- 72: TelemetryLogAdmin,
- 73: TpConnectionAdmin,
- 74: TransactionGtidTag,
- 75: Trigger,
- 76: Update,
- 77: Usage,
- 78: VersionTokenAdmin,
- 79: XaRecoverAdmin,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/mysql/permissions.yaml b/pkg/analyzer/analyzers/mysql/permissions.yaml
deleted file mode 100644
index 59f03949f379..000000000000
--- a/pkg/analyzer/analyzers/mysql/permissions.yaml
+++ /dev/null
@@ -1,80 +0,0 @@
-permissions:
- - alter
- - alter_routine
- - allow_nonexistent_definer
- - application_password_admin
- - audit_abort_exempt
- - audit_admin
- - authentication_policy_admin
- - backup_admin
- - binlog_admin
- - binlog_encryption_admin
- - clone_admin
- - connection_admin
- - create
- - create_role
- - create_routine
- - create_tablespace
- - create_temporary_tables
- - create_user
- - create_view
- - delete
- - drop
- - drop_role
- - encryption_key_admin
- - event
- - execute
- - file
- - firewall_admin
- - firewall_exempt
- - firewall_user
- - flush_optimizer_costs
- - flush_status
- - flush_tables
- - flush_user_resources
- - grant_option
- - group_replication_admin
- - group_replication_stream
- - index
- - innodb_redo_log_archive
- - innodb_redo_log_enable
- - insert
- - locking_tables
- - masking_dictionaries_admin
- - ndb_stored_user
- - passwordless_user_admin
- - persist_ro_variables_admin
- - process
- - proxy
- - references
- - reload
- - replication_applier
- - replication_client
- - replication_slave
- - replication_slave_admin
- - resource_group_admin
- - resource_group_user
- - role_admin
- - select
- - sensitive_variables_observer
- - service_connection_admin
- - session_variables_admin
- - set_any_definer
- - set_user_id
- - show_databases
- - show_routine
- - show_view
- - shutdown
- - skip_query_rewrite
- - super
- - system_user
- - system_variables_admin
- - table_encryption_admin
- - telemetry_log_admin
- - tp_connection_admin
- - transaction_gtid_tag
- - trigger
- - update
- - usage
- - version_token_admin
- - xa_recover_admin
diff --git a/pkg/analyzer/analyzers/mysql/scopes.go b/pkg/analyzer/analyzers/mysql/scopes.go
deleted file mode 100644
index 012e147beaa5..000000000000
--- a/pkg/analyzer/analyzers/mysql/scopes.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package mysql
-
-type PrivTypes struct {
- Global bool
- Database bool
- Table bool
- Column bool
- Routine bool
- Proxy bool
- Dynamic bool
-}
-
-// https://dev.mysql.com/doc/refman/8.0/en/grant.html#grant-global-privileges:~:text=%27localhost%27%3B-,Privileges%20Supported%20by%20MySQL,-The%20following%20tables
-var SCOPES = map[string]PrivTypes{
- // Static privs
- "ALTER": {Global: true, Database: true, Table: true},
- "ALTER ROUTINE": {Global: true, Database: true, Routine: true},
- "CREATE": {Global: true, Database: true, Table: true},
- "CREATE ROLE": {Global: true},
- "CREATE ROUTINE": {Global: true, Database: true},
- "CREATE TABLESPACE": {Global: true},
- "CREATE TEMPORARY TABLES": {Global: true, Database: true},
- "CREATE USER": {Global: true},
- "CREATE VIEW": {Global: true, Database: true, Table: true},
- "DELETE": {Global: true, Database: true, Table: true},
- "DROP": {Global: true, Database: true, Table: true},
- "DROP ROLE": {Global: true},
- "EVENT": {Global: true, Database: true},
- "EXECUTE": {Global: true, Database: true, Routine: true},
- "FILE": {Global: true},
- "GRANT OPTION": {Global: true, Database: true, Table: true, Routine: true, Proxy: true}, // Not granted on ALL PRIVILEGES
- "INDEX": {Global: true, Database: true, Table: true},
- "INSERT": {Global: true, Database: true, Table: true, Column: true},
- "LOCK TABLES": {Global: true, Database: true},
- "PROCESS": {Global: true},
- "PROXY": {Proxy: true}, // Not granted on ALL PRIVILEGES
- "REFERENCES": {Global: true, Database: true, Table: true, Column: true},
- "RELOAD": {Global: true},
- "REPLICATION CLIENT": {Global: true},
- "REPLICATION SLAVE": {Global: true},
- "SELECT": {Global: true, Database: true, Table: true, Column: true},
- "SHOW DATABASES": {Global: true},
- "SHOW VIEW": {Global: true, Database: true, Table: true},
- "SHUTDOWN": {Global: true},
- "SUPER": {Global: true},
- "TRIGGER": {Global: true, Database: true, Table: true},
- "UPDATE": {Global: true, Database: true, Table: true, Column: true},
-
- // This is a special case, it's not a real privilege
- "USAGE": {Global: true, Database: true, Table: true, Column: true, Routine: true},
-
- // Dynamic privs
- "ALLOW_NONEXISTENT_DEFINER": {Global: true, Dynamic: true},
- "APPLICATION_PASSWORD_ADMIN": {Global: true, Dynamic: true},
- "AUDIT_ABORT_EXEMPT": {Global: true, Dynamic: true},
- "AUDIT_ADMIN": {Global: true, Dynamic: true},
- "AUTHENTICATION_POLICY_ADMIN": {Global: true, Dynamic: true},
- "BACKUP_ADMIN": {Global: true, Dynamic: true},
- "BINLOG_ADMIN": {Global: true, Dynamic: true},
- "BINLOG_ENCRYPTION_ADMIN": {Global: true, Dynamic: true},
- "CLONE_ADMIN": {Global: true, Dynamic: true},
- "CONNECTION_ADMIN": {Global: true, Dynamic: true},
- "ENCRYPTION_KEY_ADMIN": {Global: true, Dynamic: true},
- "FIREWALL_ADMIN": {Global: true, Dynamic: true},
- "FIREWALL_EXEMPT": {Global: true, Dynamic: true},
- "FIREWALL_USER": {Global: true, Dynamic: true},
- "FLUSH_OPTIMIZER_COSTS": {Global: true, Dynamic: true},
- "FLUSH_STATUS": {Global: true, Dynamic: true},
- "FLUSH_TABLES": {Global: true, Dynamic: true},
- "FLUSH_USER_RESOURCES": {Global: true, Dynamic: true},
- "GROUP_REPLICATION_ADMIN": {Global: true, Dynamic: true},
- "GROUP_REPLICATION_STREAM": {Global: true, Dynamic: true},
- "INNODB_REDO_LOG_ARCHIVE": {Global: true, Dynamic: true},
- "INNODB_REDO_LOG_ENABLE": {Global: true, Dynamic: true},
- "MASKING_DICTIONARIES_ADMIN": {Global: true, Dynamic: true},
- "NDB_STORED_USER": {Global: true, Dynamic: true},
- "PASSWORDLESS_USER_ADMIN": {Global: true, Dynamic: true},
- "PERSIST_RO_VARIABLES_ADMIN": {Global: true, Dynamic: true},
- "REPLICATION_APPLIER": {Global: true, Dynamic: true},
- "REPLICATION_SLAVE_ADMIN": {Global: true, Dynamic: true},
- "RESOURCE_GROUP_ADMIN": {Global: true, Dynamic: true},
- "RESOURCE_GROUP_USER": {Global: true, Dynamic: true},
- "ROLE_ADMIN": {Global: true, Dynamic: true},
- "SENSITIVE_VARIABLES_OBSERVER": {Global: true, Dynamic: true},
- "SERVICE_CONNECTION_ADMIN": {Global: true, Dynamic: true},
- "SESSION_VARIABLES_ADMIN": {Global: true, Dynamic: true},
- "SET_ANY_DEFINER": {Global: true, Dynamic: true},
- "SET_USER_ID": {Global: true, Dynamic: true},
- "SHOW_ROUTINE": {Global: true, Dynamic: true},
- "SKIP_QUERY_REWRITE": {Global: true, Dynamic: true},
- "SYSTEM_USER": {Global: true, Dynamic: true},
- "SYSTEM_VARIABLES_ADMIN": {Global: true, Dynamic: true},
- "TABLE_ENCRYPTION_ADMIN": {Global: true, Dynamic: true},
- "TELEMETRY_LOG_ADMIN": {Global: true, Dynamic: true},
- "TP_CONNECTION_ADMIN": {Global: true, Dynamic: true},
- "TRANSACTION_GTID_TAG": {Global: true, Dynamic: true},
- "VERSION_TOKEN_ADMIN": {Global: true, Dynamic: true},
- "XA_RECOVER_ADMIN": {Global: true, Dynamic: true},
-}
diff --git a/pkg/analyzer/analyzers/netlify/models.go b/pkg/analyzer/analyzers/netlify/models.go
deleted file mode 100644
index 73a9f5c67e2e..000000000000
--- a/pkg/analyzer/analyzers/netlify/models.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package netlify
-
-import "sync"
-
-type ResourceType string
-
-func (r ResourceType) String() string {
- return string(r)
-}
-
-const (
- CurrentUser ResourceType = "User"
- Token ResourceType = "Token"
- Site ResourceType = "Site"
- SiteFile ResourceType = "Site File"
- SiteEnvVar ResourceType = "Site Env Variable"
- SiteSnippet ResourceType = "Site Snippet"
- SiteDeploy ResourceType = "Site Deploy"
- SiteDeployedBranch ResourceType = "Site Deployed Branch"
- SiteBuild ResourceType = "Site Build"
- SiteDevServer ResourceType = "Site Dev Server"
- SiteBuildHook ResourceType = "Site Build Hook"
- SiteDevServerHook ResourceType = "Site Dev Server Hook"
- SiteServiceInstance ResourceType = "Site Service Instance"
- SiteFunction ResourceType = "Site Function"
- SiteForm ResourceType = "Site Form"
- SiteSubmission ResourceType = "Site Submission"
- SiteTrafficSplit ResourceType = "Site Traffic Split"
- DNSZone ResourceType = "DNS Zone"
- Service ResourceType = "Service"
-)
-
-type SecretInfo struct {
- mu sync.RWMutex
-
- UserInfo User
- Resources []NetlifyResource
-}
-
-func (s *SecretInfo) appendResource(resource NetlifyResource) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- s.Resources = append(s.Resources, resource)
-}
-
-// listResourceByType returns a list of resources matching the given type.
-func (s *SecretInfo) listResourceByType(resourceType ResourceType) []NetlifyResource {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- resources := make([]NetlifyResource, 0, len(s.Resources))
- for _, resource := range s.Resources {
- if resource.Type == resourceType.String() {
- resources = append(resources, resource)
- }
- }
-
- return resources
-}
-
-type User struct {
- ID string `json:"id"`
- Name string `json:"full_name"`
- Email string `json:"email"`
- AccountID string `json:"account_id"`
- LastLogin string `json:"last_login"`
-}
-
-type NetlifyResource struct {
- ID string
- Name string
- Type string
- Metadata map[string]string
- Parent *NetlifyResource
-}
-
-type token struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Personal bool `json:"personal"`
- ExpiresAt string `json:"expires_at"`
-}
-
-type site struct {
- SiteID string `json:"site_id"`
- Name string `json:"name"`
- Url string `json:"url"`
- AdminUrl string `json:"admin_url"`
- RepoUrl string `json:"repo_url"`
-}
-
-type file struct {
- ID string `json:"id"`
- Path string `json:"path"`
- MimeType string `json:"mime_type"`
-}
-
-type envVariable struct {
- Key string `json:"key"`
- Scopes []string `json:"scopes"`
- Values []struct {
- ID string `json:"id"`
- Value string `json:"value"`
- } `json:"values"`
-}
-
-type snippet struct {
- ID string `json:"id"`
- Title string `json:"title"`
-}
-
-type deploy struct {
- ID string `json:"id"`
- Name string `json:"name"`
- BuildID string `json:"build_id"`
- State string `json:"state"`
- Url string `json:"url"`
-}
-
-type deployedBranch struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Slug string `json:"slug"`
-}
-
-type build struct {
- ID string `json:"id"`
- DeployState string `json:"deploy_state"`
-}
-
-type devServer struct {
- ID string `json:"id"`
- Title string `json:"title"`
-}
-
-type buildHook struct {
- ID string `json:"id"`
- Title string `json:"title"`
- Branch string `json:"branch"`
-}
-
-type serviceInstance struct {
- ID string `json:"id"`
- ServiceName string `json:"service_name"`
- Url string `json:"url"`
-}
-
-type function struct {
- ID string `json:"id"`
- Provider string `json:"provider"`
-}
-
-// this handle response of 3 API's
-type formSubmissionSplitInfo struct {
- ID string `json:"id"`
- Name string `json:"name"`
-}
-
-type dnsZone struct {
- ID string `json:"id"`
- Name string `json:"name"`
-}
-
-type service struct {
- ID string `json:"id"`
- Name string `json:"name"`
- ServicePath string `json:"service_path"`
-}
diff --git a/pkg/analyzer/analyzers/netlify/netlify.go b/pkg/analyzer/analyzers/netlify/netlify.go
deleted file mode 100644
index e2380f68ff81..000000000000
--- a/pkg/analyzer/analyzers/netlify/netlify.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go netlify
-package netlify
-
-import (
- "fmt"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeNetlify
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, exist := credInfo["key"]
- if !exist {
- return nil, fmt.Errorf("key not found in credential info")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- // just print the error in cli and continue as a partial success
- color.Red("[x] Error : %s", err.Error())
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- color.Green("[!] Valid Netlify API key\n\n")
-
- printUserInfo(info.UserInfo)
- printTokenInfo(info.listResourceByType(Token))
- printResources(info.Resources)
-
- color.Yellow("\n[i] Expires: %s", "N/A (Refer to Token Information Table)")
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- client := analyzers.NewAnalyzeClient(cfg)
-
- var secretInfo = &SecretInfo{}
-
- if err := captureUserInfo(client, key, secretInfo); err != nil {
- return nil, err
- }
-
- if err := captureTokens(client, key, secretInfo); err != nil {
- return nil, err
- }
-
- if err := captureResources(client, key, secretInfo); err != nil {
- return secretInfo, err
- }
-
- return secretInfo, nil
-}
-
-// secretInfoToAnalyzerResult translate secret info to Analyzer Result
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeNetlify,
- Metadata: map[string]any{},
- Bindings: make([]analyzers.Binding, 0),
- }
-
- // extract information from resource to create bindings and append to result bindings
- for _, resource := range info.Resources {
- binding := analyzers.Binding{
- Resource: analyzers.Resource{
- Name: resource.Name,
- FullyQualifiedName: fmt.Sprintf("netlify/%s/%s", resource.Type, resource.ID), // e.g: netlify/site/123
- Type: resource.Type,
- Metadata: map[string]any{}, // to avoid panic
- },
- Permission: analyzers.Permission{
- Value: PermissionStrings[FullAccess], // no fine grain access
- },
- }
-
- if resource.Parent != nil {
- binding.Resource.Parent = &analyzers.Resource{
- Name: resource.Parent.Name,
- FullyQualifiedName: resource.Parent.ID,
- Type: resource.Parent.Type,
- // not copying parent metadata
- }
- }
-
- for key, value := range resource.Metadata {
- binding.Resource.Metadata[key] = value
- }
-
- result.Bindings = append(result.Bindings, binding)
- }
-
- return &result
-}
-
-// cli print functions
-func printUserInfo(user User) {
- color.Yellow("[i] User Information:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Name", "Email", "Account ID", "Last Login At"})
- t.AppendRow(table.Row{color.GreenString(user.Name), color.GreenString(user.Email), color.GreenString(user.AccountID), color.GreenString(user.LastLogin)})
-
- t.Render()
-}
-
-func printTokenInfo(tokens []NetlifyResource) {
- color.Yellow("[i] Tokens Information:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"ID", "Name", "Personal", "Expires At"})
- for _, token := range tokens {
- t.AppendRow(table.Row{color.GreenString(token.ID), color.GreenString(token.Name), color.GreenString(token.Metadata[tokenPersonal]), color.GreenString(token.Metadata[tokenExpiresAt])})
- }
- t.Render()
-}
-
-func printResources(resources []NetlifyResource) {
- color.Yellow("[i] Resources:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Name", "Type"})
- for _, resource := range resources {
- // skip token type resource as we will print them separately
- if resource.Type == Token.String() {
- continue
- }
-
- t.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/netlify/netlify_test.go b/pkg/analyzer/analyzers/netlify/netlify_test.go
deleted file mode 100644
index e3a024283219..000000000000
--- a/pkg/analyzer/analyzers/netlify/netlify_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package netlify
-
-import (
- _ "embed"
- "encoding/json"
- "fmt"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed result_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "analyzers1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- key := testSecrets.MustGetField("NETLIFY_PAT")
-
- tests := []struct {
- name string
- key string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid netlify personal access token",
- key: key,
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- fmt.Println(string(gotJSON))
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/netlify/permissions.go b/pkg/analyzer/analyzers/netlify/permissions.go
deleted file mode 100644
index 3f8205d0a4e3..000000000000
--- a/pkg/analyzer/analyzers/netlify/permissions.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package netlify
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- FullAccess Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- FullAccess: "full_access",
- }
-
- StringToPermission = map[string]Permission{
- "full_access": FullAccess,
- }
-
- PermissionIDs = map[Permission]int{
- FullAccess: 1,
- }
-
- IdToPermission = map[int]Permission{
- 1: FullAccess,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/netlify/permissions.yaml b/pkg/analyzer/analyzers/netlify/permissions.yaml
deleted file mode 100644
index fb4bbdffbff1..000000000000
--- a/pkg/analyzer/analyzers/netlify/permissions.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-permissions:
- - full_access
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/netlify/requests.go b/pkg/analyzer/analyzers/netlify/requests.go
deleted file mode 100644
index e08035e29b8e..000000000000
--- a/pkg/analyzer/analyzers/netlify/requests.go
+++ /dev/null
@@ -1,710 +0,0 @@
-package netlify
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strconv"
- "strings"
- "sync"
-)
-
-var (
- apiEndpoints = map[ResourceType]string{
- CurrentUser: "https://api.netlify.com/api/v1/user",
- Token: "https://app.netlify.com/access-control/bb-api/api/v1/oauth/applications", // undocumented API - return personal tokens with metadata
- Site: "https://api.netlify.com/api/v1/sites",
- SiteFile: "https://api.netlify.com/api/v1/sites/%s/files", // require site id
- SiteEnvVar: "https://api.netlify.com/api/v1/sites/%s/env", // require site id
- SiteSnippet: "https://api.netlify.com/api/v1/sites/%s/snippets", // require site id
- SiteDeploy: "https://api.netlify.com/api/v1/sites/%s/deploys", // require site id
- SiteDeployedBranch: "https://api.netlify.com/api/v1/sites/%s/deployed-branches", // require site id
- SiteBuild: "https://api.netlify.com/api/v1/sites/%s/builds", // require site id
- SiteDevServer: "https://api.netlify.com/api/v1/sites/%s/dev_servers", // require site id
- SiteBuildHook: "https://api.netlify.com/api/v1/sites/%s/build_hooks", // require site id
- SiteDevServerHook: "https://api.netlify.com/api/v1/sites/%s/dev_server_hooks", // require site id
- SiteServiceInstance: "https://api.netlify.com/api/v1/sites/%s/service-instances", // require site id
- SiteFunction: "https://api.netlify.com/api/v1/sites/%s/functions", // require site id
- SiteForm: "https://api.netlify.com/api/v1/sites/%s/forms", // require site id
- SiteSubmission: "https://api.netlify.com/api/v1/sites/%s/submissions", // require site id
- SiteTrafficSplit: "https://api.netlify.com/api/v1/sites/%s/traffic_splits", // require site id
- DNSZone: "https://api.netlify.com/api/v1/dns_zones",
- Service: "https://api.netlify.com/api/v1/services",
-
- /*
- TODO APIs:
- - https://api.netlify.com/api/v1/sites/{site_id}/metadata (Just return key and values added as metadata for a site)
- - https://api.netlify.com/api/v1/sites/{site_id}/assets/{asset_id} (Require asset id - No API to list assets)
- - https://api.netlify.com/api/v1/deploy_keys (Have id and a public key in response only)
- */
- }
-
- // metadata keys - should always start with resource name
- tokenPersonal = "personal"
- tokenExpiresAt = "expires_at"
- siteUrl = "site_url"
- siteAdminUrl = "site_admin_url"
- siteRepoUrl = "site_repo_url"
- fileMimeType = "site_mime_type"
- deployBuildID = "deploy_build_id"
- deployState = "deploy_state"
- deployUrl = "deploy_url"
- deployedBranchSlug = "deployed_branch_slug"
- buildHookBranch = "build_hook_branch"
- serviceInstanceUrl = "service_instance_url"
-)
-
-// makeNetlifyRequest send the API request to passed url with passed key as personal access token and return response body and status code
-func makeNetlifyRequest(client *http.Client, endpoint, key string) ([]byte, int, error) {
- // create request
- req, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody)
- if err != nil {
- return nil, 0, err
- }
-
- // add key in the header
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, 0, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- responseBodyByte, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, 0, err
- }
-
- return responseBodyByte, resp.StatusCode, nil
-}
-
-// captureResources try to capture all the resource that the key can access
-func captureResources(client *http.Client, key string, secretInfo *SecretInfo) error {
- var (
- wg sync.WaitGroup
- errAggWg sync.WaitGroup
- aggregatedErrs = make([]error, 0)
- errChan = make(chan error, 1)
- )
-
- errAggWg.Add(1)
- go func() {
- defer errAggWg.Done()
- for err := range errChan {
- aggregatedErrs = append(aggregatedErrs, err)
- }
- }()
-
- // helper to launch tasks concurrently.
- launchTask := func(task func() error) {
- wg.Add(1)
- go func() {
- defer wg.Done()
- if err := task(); err != nil {
- errChan <- err
- }
- }()
- }
-
- // capture top level resources
- if err := captureSites(client, key, secretInfo); err != nil {
- return err
- }
-
- // capture all sub resources of all sites
- sites := secretInfo.listResourceByType(Site)
- for _, site := range sites {
- launchTask(func() error { return captureSiteFiles(client, key, site, secretInfo) })
- launchTask(func() error { return captureSiteEnvVar(client, key, site, secretInfo) })
- launchTask(func() error { return captureSiteSnippets(client, key, site, secretInfo) })
- launchTask(func() error { return captureSiteDeploys(client, key, site, secretInfo) })
- launchTask(func() error { return captureSiteDeployedBranches(client, key, site, secretInfo) })
- launchTask(func() error { return captureSiteBuilds(client, key, site, secretInfo) })
- launchTask(func() error { return captureSiteDevServers(client, key, site, secretInfo) })
- launchTask(func() error { return captureSiteBuildHooks(client, key, site, secretInfo) })
- launchTask(func() error { return captureSiteDevServerHooks(client, key, site, secretInfo) })
- launchTask(func() error { return captureSiteServiceInstances(client, key, site, secretInfo) })
- launchTask(func() error { return captureSiteFunctions(client, key, site, secretInfo) })
- launchTask(func() error { return captureSiteFormSubmissionSplitInfo(client, key, site, SiteForm, secretInfo) })
- launchTask(func() error { return captureSiteFormSubmissionSplitInfo(client, key, site, SiteSubmission, secretInfo) })
- launchTask(func() error {
- return captureSiteFormSubmissionSplitInfo(client, key, site, SiteTrafficSplit, secretInfo)
- })
- }
-
- launchTask(func() error { return captureDNSZones(client, key, secretInfo) })
- launchTask(func() error { return captureServices(client, key, secretInfo) })
-
- wg.Wait()
- close(errChan)
- errAggWg.Wait()
-
- if len(aggregatedErrs) > 0 {
- return errors.Join(aggregatedErrs...)
- }
-
- return nil
-}
-
-func captureUserInfo(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, apiEndpoints[CurrentUser], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var user User
-
- if err := json.Unmarshal(respBody, &user); err != nil {
- return err
- }
-
- secretInfo.UserInfo = user
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d for API: %s", statusCode, apiEndpoints[CurrentUser])
- }
-}
-
-func captureTokens(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, apiEndpoints[Token], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var tokens []token
-
- if err := json.Unmarshal(respBody, &tokens); err != nil {
- return err
- }
-
- for _, token := range tokens {
- if token.ExpiresAt == "" {
- token.ExpiresAt = "never"
- }
-
- resource := NetlifyResource{
- ID: token.ID,
- Name: token.Name,
- Type: Token.String(),
- Metadata: map[string]string{
- tokenExpiresAt: token.ExpiresAt,
- tokenPersonal: strconv.FormatBool(token.Personal),
- },
- }
-
- secretInfo.Resources = append(secretInfo.Resources, resource)
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d for API: %s", statusCode, apiEndpoints[Token])
- }
-}
-
-func captureSites(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, apiEndpoints[Site], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var sites []site
-
- if err := json.Unmarshal(respBody, &sites); err != nil {
- return err
- }
-
- for _, site := range sites {
- secretInfo.appendResource(NetlifyResource{
- ID: site.SiteID,
- Name: site.Name,
- Type: Site.String(),
- Metadata: map[string]string{
- siteUrl: site.Url,
- siteAdminUrl: site.AdminUrl,
- siteRepoUrl: site.RepoUrl,
- },
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteFiles(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteFile], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var files []file
-
- if err := json.Unmarshal(respBody, &files); err != nil {
- return err
- }
-
- for _, file := range files {
- secretInfo.appendResource(NetlifyResource{
- ID: site.ID + "/" + file.ID, // combine site id with file id to make it unique
- Name: file.Path,
- Type: SiteFile.String(),
- Metadata: map[string]string{
- fileMimeType: file.MimeType,
- },
- Parent: &site,
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteEnvVar(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteEnvVar], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var envVariables []envVariable
-
- if err := json.Unmarshal(respBody, &envVariables); err != nil {
- return err
- }
-
- for _, envVar := range envVariables {
- // multiple values exist for each env variable, so we append separate resource for each value
- for _, value := range envVar.Values {
- secretInfo.appendResource(NetlifyResource{
- ID: envVar.Key + "/" + value.ID,
- Name: envVar.Key + "/***" + value.Value[len(value.Value)-4:], // append last 4 characters of value with key to make it unique
- Type: SiteEnvVar.String(),
- Metadata: map[string]string{
- "value": value.Value,
- "scopes": strings.Join(envVar.Scopes, ";"),
- },
- Parent: &site,
- })
- }
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteSnippets(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteSnippet], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var snippets []snippet
-
- if err := json.Unmarshal(respBody, &snippets); err != nil {
- return err
- }
-
- for _, snippet := range snippets {
- secretInfo.appendResource(NetlifyResource{
- ID: snippet.ID,
- Name: snippet.Title,
- Type: SiteSnippet.String(),
- Parent: &site,
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteDeploys(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteDeploy], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var deploys []deploy
-
- if err := json.Unmarshal(respBody, &deploys); err != nil {
- return err
- }
-
- for _, deploy := range deploys {
- secretInfo.appendResource(NetlifyResource{
- ID: site.ID + "/deploy/" + deploy.ID,
- Name: deploy.Name,
- Type: SiteDeploy.String(),
- Metadata: map[string]string{
- deployBuildID: deploy.BuildID,
- deployState: deploy.State,
- deployUrl: deploy.Url,
- },
- Parent: &site,
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteDeployedBranches(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteDeployedBranch], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var deployedBranches []deployedBranch
-
- if err := json.Unmarshal(respBody, &deployedBranches); err != nil {
- return err
- }
-
- for _, deployedBranch := range deployedBranches {
- secretInfo.appendResource(NetlifyResource{
- ID: deployedBranch.ID,
- Name: deployedBranch.Name,
- Type: SiteDeployedBranch.String(),
- Metadata: map[string]string{
- deployedBranchSlug: deployedBranch.Slug,
- },
- Parent: &site,
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteBuilds(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteBuild], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var builds []build
-
- if err := json.Unmarshal(respBody, &builds); err != nil {
- return err
- }
-
- for _, build := range builds {
- secretInfo.appendResource(NetlifyResource{
- ID: build.ID,
- Name: build.ID + "/state/" + build.DeployState, // no specific name
- Type: SiteBuild.String(),
- Parent: &site,
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteDevServers(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteDevServer], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var devServers []devServer
-
- if err := json.Unmarshal(respBody, &devServers); err != nil {
- return err
- }
-
- for _, devServer := range devServers {
- secretInfo.appendResource(NetlifyResource{
- ID: devServer.ID,
- Name: devServer.Title,
- Type: SiteDevServer.String(),
- Parent: &site,
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteBuildHooks(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteBuildHook], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var hooks []buildHook
-
- if err := json.Unmarshal(respBody, &hooks); err != nil {
- return err
- }
-
- for _, hook := range hooks {
- secretInfo.appendResource(NetlifyResource{
- ID: hook.ID,
- Name: hook.Title,
- Type: SiteBuildHook.String(),
- Metadata: map[string]string{
- buildHookBranch: hook.Branch,
- },
- Parent: &site,
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteDevServerHooks(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteDevServerHook], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var devServerHooks []buildHook
-
- if err := json.Unmarshal(respBody, &devServerHooks); err != nil {
- return err
- }
-
- for _, hook := range devServerHooks {
- secretInfo.appendResource(NetlifyResource{
- ID: hook.ID,
- Name: hook.Title,
- Type: SiteDevServerHook.String(),
- Metadata: map[string]string{
- buildHookBranch: hook.Branch,
- },
- Parent: &site,
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteServiceInstances(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteServiceInstance], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var serviceInstances []serviceInstance
-
- if err := json.Unmarshal(respBody, &serviceInstances); err != nil {
- return err
- }
-
- for _, instance := range serviceInstances {
- secretInfo.appendResource(NetlifyResource{
- ID: instance.ID,
- Name: instance.ServiceName + "/instance/" + instance.ID, // no specific name
- Type: SiteServiceInstance.String(),
- Metadata: map[string]string{
- serviceInstanceUrl: instance.Url,
- },
- Parent: &site,
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteFunctions(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteFunction], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var data function
-
- if err := json.Unmarshal(respBody, &data); err != nil {
- return err
- }
-
- secretInfo.appendResource(NetlifyResource{
- ID: data.ID,
- Name: "function/" + data.ID + "/provider/" + data.Provider, // no specific name
- Type: SiteFunction.String(),
- Parent: &site,
- })
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureSiteFormSubmissionSplitInfo(client *http.Client, key string, site NetlifyResource, resType ResourceType, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[resType], site.ID), key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var formSubSplitInfos []formSubmissionSplitInfo
-
- if err := json.Unmarshal(respBody, &formSubSplitInfos); err != nil {
- return err
- }
-
- for _, info := range formSubSplitInfos {
- secretInfo.appendResource(NetlifyResource{
- ID: info.ID,
- Name: info.Name, // no specific name
- Type: resType.String(),
- Parent: &site,
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureDNSZones(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, apiEndpoints[DNSZone], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var dnsZones []dnsZone
-
- if err := json.Unmarshal(respBody, &dnsZones); err != nil {
- return err
- }
-
- for _, dnsZone := range dnsZones {
- secretInfo.appendResource(NetlifyResource{
- ID: dnsZone.ID,
- Name: dnsZone.Name,
- Type: DNSZone.String(),
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
-
-func captureServices(client *http.Client, key string, secretInfo *SecretInfo) error {
- respBody, statusCode, err := makeNetlifyRequest(client, apiEndpoints[Service], key)
- if err != nil {
- return err
- }
-
- switch statusCode {
- case http.StatusOK:
- var services []service
-
- if err := json.Unmarshal(respBody, &services); err != nil {
- return err
- }
-
- for _, service := range services {
- secretInfo.appendResource(NetlifyResource{
- ID: service.ID,
- Name: service.Name,
- Type: Service.String(),
- })
- }
-
- return nil
- case http.StatusUnauthorized:
- return fmt.Errorf("invalid/expired personal access token")
- default:
- return fmt.Errorf("unexpected status code: %d", statusCode)
- }
-}
diff --git a/pkg/analyzer/analyzers/netlify/result_output.json b/pkg/analyzer/analyzers/netlify/result_output.json
deleted file mode 100644
index 8c039061cb6e..000000000000
--- a/pkg/analyzer/analyzers/netlify/result_output.json
+++ /dev/null
@@ -1,726 +0,0 @@
-{
- "AnalyzerType": 34,
- "Bindings": [
- {
- "Resource": {
- "Name": "/assets/404-bp-rpyh2.js",
- "FullyQualifiedName": "netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//assets/404-bp-rpyh2.js",
- "Type": "Site File",
- "Metadata": {
- "site_mime_type": "application/javascript"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "/assets/about-c6ru7nfs.js",
- "FullyQualifiedName": "netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//assets/about-c6ru7nfs.js",
- "Type": "Site File",
- "Metadata": {
- "site_mime_type": "application/javascript"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "/assets/index-bjt0jjds.css",
- "FullyQualifiedName": "netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//assets/index-bjt0jjds.css",
- "Type": "Site File",
- "Metadata": {
- "site_mime_type": "text/css"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "/assets/index-csbqlcvs.js",
- "FullyQualifiedName": "netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//assets/index-csbqlcvs.js",
- "Type": "Site File",
- "Metadata": {
- "site_mime_type": "application/javascript"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "/index.html",
- "FullyQualifiedName": "netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//index.html",
- "Type": "Site File",
- "Metadata": {
- "site_mime_type": "text/html"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "/netlify.toml",
- "FullyQualifiedName": "netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//netlify.toml",
- "Type": "Site File",
- "Metadata": {
- "site_mime_type": "application/octet-stream"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "680a1332fb8af883c4da6666/state/ready",
- "FullyQualifiedName": "netlify/Site Build/680a1332fb8af883c4da6666",
- "Type": "Site Build",
- "Metadata": {},
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Addon example",
- "FullyQualifiedName": "netlify/Service/5ec5b30682bb8a00bad573ee",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "DataStax Astra",
- "FullyQualifiedName": "netlify/Service/5fadc1941f0b1600909ffe94",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "DatoCMS Local",
- "FullyQualifiedName": "netlify/Service/5bc77c2bac7ff24e6152b43c",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "DatoCMS Staging",
- "FullyQualifiedName": "netlify/Service/5bc77adbac7ff24e6152b43b",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Demo add-on",
- "FullyQualifiedName": "netlify/Service/5c1abf2cac7ff2374d58fce2",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "EXAMPLE_KEY/***ALUE",
- "FullyQualifiedName": "netlify/Site Env Variable/EXAMPLE_KEY/0016840d-61c5-4e4a-aab4-8f9d73125846",
- "Type": "Site Env Variable",
- "Metadata": {
- "scopes": "builds;functions;post_processing;runtime",
- "value": "EXAMPLE_VALUE"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "EXAMPLE_KEY_2/***anch",
- "FullyQualifiedName": "netlify/Site Env Variable/EXAMPLE_KEY_2/c5d9d7f5-52f9-48a8-a218-a80d294f347e",
- "Type": "Site Env Variable",
- "Metadata": {
- "scopes": "builds;functions;runtime",
- "value": "****************anch"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "EXAMPLE_KEY_2/***ocal",
- "FullyQualifiedName": "netlify/Site Env Variable/EXAMPLE_KEY_2/b9df8f4a-7a18-4cc1-bf80-e0affa32569b",
- "Type": "Site Env Variable",
- "Metadata": {
- "scopes": "builds;functions;runtime",
- "value": "local"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "EXAMPLE_KEY_2/***od_1",
- "FullyQualifiedName": "netlify/Site Env Variable/EXAMPLE_KEY_2/1973b53f-60ab-40ce-ac23-20f6f2241fb3",
- "Type": "Site Env Variable",
- "Metadata": {
- "scopes": "builds;functions;runtime",
- "value": "****************od_1"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "EXAMPLE_KEY_2/***ploy",
- "FullyQualifiedName": "netlify/Site Env Variable/EXAMPLE_KEY_2/dab3426a-fb1e-405c-b89c-5143650e510e",
- "Type": "Site Env Variable",
- "Metadata": {
- "scopes": "builds;functions;runtime",
- "value": "****************ploy"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "EXAMPLE_KEY_2/***thon",
- "FullyQualifiedName": "netlify/Site Env Variable/EXAMPLE_KEY_2/658308b6-0cef-4987-a2a7-3235997af270",
- "Type": "Site Env Variable",
- "Metadata": {
- "scopes": "builds;functions;runtime",
- "value": "****************thon"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "EXAMPLE_KEY_2/***view",
- "FullyQualifiedName": "netlify/Site Env Variable/EXAMPLE_KEY_2/a210fa75-1194-42a2-8ff9-788bdf70d2b3",
- "Type": "Site Env Variable",
- "Metadata": {
- "scopes": "builds;functions;runtime",
- "value": "****************view"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Expired Token",
- "FullyQualifiedName": "netlify/Token/680b33106d9ae981575b4dec",
- "Type": "Token",
- "Metadata": {
- "expires_at": "2025-04-26T00:00:01.262Z",
- "personal": "true"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Express Example",
- "FullyQualifiedName": "netlify/Service/5b96e429ac7ff24ff6916ae1",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Fauna DB staging",
- "FullyQualifiedName": "netlify/Service/5bbbea43ac7ff23902cc2a64",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "FaunaDB Cloud",
- "FullyQualifiedName": "netlify/Service/5bcf902fac7ff255bfc36233",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Get off my lawn",
- "FullyQualifiedName": "netlify/Service/5ce6f8be82bb8a00b9940dfd",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Hasura GraphQL Engine",
- "FullyQualifiedName": "netlify/Service/5c196638ac7ff255c853647e",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Joy Staging",
- "FullyQualifiedName": "netlify/Service/5d23c4e682bb8a00ba311f23",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Netlify CMS Media Manager",
- "FullyQualifiedName": "netlify/Service/5b9addcdac7ff27e11b0d4e4",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Nimbella",
- "FullyQualifiedName": "netlify/Service/5f6d15de1f0b1600903dde32",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Nimbella Staging",
- "FullyQualifiedName": "netlify/Service/5d9e587082bb8a00bb94943f",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "No Exp Token",
- "FullyQualifiedName": "netlify/Token/680b32c6bccfc08cd7732add",
- "Type": "Token",
- "Metadata": {
- "expires_at": "never",
- "personal": "true"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Takeshape CMS",
- "FullyQualifiedName": "netlify/Service/5c9934a882bb8a00bc657db7",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Takeshape CMS staging",
- "FullyQualifiedName": "netlify/Service/5c798b4582bb8a00b7504197",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "VGS Staging",
- "FullyQualifiedName": "netlify/Service/5be1c5bfac7ff267e9ba987b",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Very Good Security",
- "FullyQualifiedName": "netlify/Service/5c6f1bbf82bb8a00bcdea659",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "View source banner addon",
- "FullyQualifiedName": "netlify/Service/5b9aef73ac7ff23d0a3fecd4",
- "Type": "Service",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "analyzer-test(do not delete)",
- "FullyQualifiedName": "netlify/Token/6810b09ab80020167d7525fe",
- "Type": "Token",
- "Metadata": {
- "expires_at": "never",
- "personal": "true"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "function//provider/",
- "FullyQualifiedName": "netlify/Site Function/",
- "Type": "Site Function",
- "Metadata": {},
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "hook1",
- "FullyQualifiedName": "netlify/Site Build Hook/680a168ae30f218cd01bd4e8",
- "Type": "Site Build Hook",
- "Metadata": {
- "build_hook_branch": "main"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "test-app",
- "FullyQualifiedName": "netlify/Token/6809f1dbfb8af846a8da644f",
- "Type": "Token",
- "Metadata": {
- "expires_at": "never",
- "personal": "false"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "test01",
- "FullyQualifiedName": "netlify/Token/6809f1b5830a5c43672123f8",
- "Type": "Token",
- "Metadata": {
- "expires_at": "2025-05-24T08:09:25.796Z",
- "personal": "true"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "netlify/Site/dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": {
- "site_admin_url": "https://app.netlify.com/sites/truffle-test-site",
- "site_repo_url": "",
- "site_url": "http://truffle-test-site.netlify.app"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "netlify/Site Deploy/dda81214-b126-43bf-9508-ae94cf9d0506/deploy/680a1332fb8af883c4da6668",
- "Type": "Site Deploy",
- "Metadata": {
- "deploy_build_id": "680a1332fb8af883c4da6666",
- "deploy_state": "ready",
- "deploy_url": "http://truffle-test-site.netlify.app"
- },
- "Parent": {
- "Name": "truffle-test-site",
- "FullyQualifiedName": "dda81214-b126-43bf-9508-ae94cf9d0506",
- "Type": "Site",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "trufflesecurity.com",
- "FullyQualifiedName": "netlify/DNS Zone/6809f163830a5c42ca212432",
- "Type": "DNS Zone",
- "Metadata": {},
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {}
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/ngrok/expected_output.json b/pkg/analyzer/analyzers/ngrok/expected_output.json
deleted file mode 100644
index ec2fdc611f4d..000000000000
--- a/pkg/analyzer/analyzers/ngrok/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":37,"Bindings":[{"Resource":{"Name":"ep_2wRn1EAlf7JqFe3RPJBRNW1IkTI","FullyQualifiedName":"endpoint/ep_2wRn1EAlf7JqFe3RPJBRNW1IkTI","Type":"endpoint","Metadata":{"bindings":["public"],"createdAt":"2025-04-30T11:37:16Z","host":"","hostport":"lightly-communal-lizard.ngrok-free.app:443","metadata":"","port":0,"proto":"https","publicURL":"https://lightly-communal-lizard.ngrok-free.app","region":"","type":"cloud","updatedAt":"2025-04-30T11:37:16Z","uri":"https://api.ngrok.com/endpoints/ep_2wRn1EAlf7JqFe3RPJBRNW1IkTI"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"rd_2wRmxH1k3oaR4HFnXkzac9uz9wr","FullyQualifiedName":"domain/rd_2wRmxH1k3oaR4HFnXkzac9uz9wr","Type":"domain","Metadata":{"createdAt":"2025-04-30T11:36:44Z","domain":"lightly-communal-lizard.ngrok-free.app","metadata":"","uri":"https://api.ngrok.com/reserved_domains/rd_2wRmxH1k3oaR4HFnXkzac9uz9wr"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"ak_2wRnGU2AMIxg7O737nC6geKeVlX","FullyQualifiedName":"api_key/ak_2wRnGU2AMIxg7O737nC6geKeVlX","Type":"api_key","Metadata":{"createdAt":"2025-04-30T11:39:17Z","description":"API Key for \"Truffle Detector\"","metadata":"","ownerID":"usr_2wRmmWE9P3ivK6dpF8sCaMd0X0V","uri":"https://api.ngrok.com/api_keys/ak_2wRnGU2AMIxg7O737nC6geKeVlX"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"ak_2wRnJq02PcmYrlH38sdE4rKZKZY","FullyQualifiedName":"api_key/ak_2wRnJq02PcmYrlH38sdE4rKZKZY","Type":"api_key","Metadata":{"createdAt":"2025-04-30T11:39:44Z","description":"API Key for \"Elliot\"","metadata":"","ownerID":"bot_2wRn8sBjuvH9ZmLbBzrKKJKFmHq","uri":"https://api.ngrok.com/api_keys/ak_2wRnJq02PcmYrlH38sdE4rKZKZY"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"cr_2wRnBCGRkEgPBKb5G6SLxr0h8tb","FullyQualifiedName":"authtoken/cr_2wRnBCGRkEgPBKb5G6SLxr0h8tb","Type":"authtoken","Metadata":{"acl":[],"createdAt":"2025-04-30T11:38:35Z","description":"Tunnel Authtoken for \"Elliot\"","metadata":"","ownerID":"bot_2wRn8sBjuvH9ZmLbBzrKKJKFmHq","uri":"https://api.ngrok.com/credentials/cr_2wRnBCGRkEgPBKb5G6SLxr0h8tb"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"cr_2wRmmYJ2BRKy3SFnusPWX5dRPVQ","FullyQualifiedName":"authtoken/cr_2wRmmYJ2BRKy3SFnusPWX5dRPVQ","Type":"authtoken","Metadata":{"acl":[],"createdAt":"2025-04-30T11:35:19Z","description":"credential for \"detectors@trufflesec.com\"","metadata":"","ownerID":"usr_2wRmmWE9P3ivK6dpF8sCaMd0X0V","uri":"https://api.ngrok.com/credentials/cr_2wRmmYJ2BRKy3SFnusPWX5dRPVQ"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"sshcr_2wRnP5xXhZ2uhXBPQcFOFqhynaa","FullyQualifiedName":"ssh_credential/sshcr_2wRnP5xXhZ2uhXBPQcFOFqhynaa","Type":"ssh_credential","Metadata":{"acl":[],"createdAt":"2025-04-30T11:40:26Z","description":"SSH Key for \"Truffle Detector\"","metadata":"","ownerID":"usr_2wRmmWE9P3ivK6dpF8sCaMd0X0V","publicKey":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCo9S+bLqFTCzDA0TxJWaiPqddDnrHojOHCOnl+ZlRcbBrG9hM8IUmaJ+ZG63NIOkaqrlHGed7MK+SLqZIqi/TkuyHwu8kkBcPCayrHdgdb9NWLpRFaWN2A67Ww+/14rPEzY7KA5EDlmWow2IPK9Ayb+J5El6NRAhLS8AChupfmRjAOxciMUTdckTI2avr5R1sOddI8cutjfvuwQvFpJI1oJLbewUxZv8gOXuqbZScIx72NiZvtCDtktVjNVm6sib129P+vD3QzCwSuNGZIv9fUcQK7Y/rmMHyjDNfvaqm8HunINBV+kDxubfbIQBMCpj/HeuUVToQ3xyfqGaON0EPa","uri":"https://api.ngrok.com/ssh_credentials/sshcr_2wRnP5xXhZ2uhXBPQcFOFqhynaa"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"bot_2wRn8sBjuvH9ZmLbBzrKKJKFmHq","FullyQualifiedName":"bot_user/bot_2wRn8sBjuvH9ZmLbBzrKKJKFmHq","Type":"bot_user","Metadata":{"active":true,"createdAt":"2025-04-30T11:38:17Z","name":"Elliot","uri":"https://api.ngrok.com/bot_users/bot_2wRn8sBjuvH9ZmLbBzrKKJKFmHq"},"Parent":null},"Permission":{"Value":"full_access","Parent":null}},{"Resource":{"Name":"usr_2wRmmWE9P3ivK6dpF8sCaMd0X0V","FullyQualifiedName":"user/usr_2wRmmWE9P3ivK6dpF8sCaMd0X0V","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"full_access","Parent":null}}],"UnboundedResources":[{"Name":"Account Plan","FullyQualifiedName":"account_plan/Free","Type":"account_plan","Metadata":null,"Parent":null}],"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/ngrok/models.go b/pkg/analyzer/analyzers/ngrok/models.go
deleted file mode 100644
index 25512ed33715..000000000000
--- a/pkg/analyzer/analyzers/ngrok/models.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package ngrok
-
-type apiKey struct {
- ID string `json:"id"`
- URI string `json:"uri"`
- Description string `json:"description"`
- Metadata string `json:"metadata"`
- OwnerID string `json:"owner_id"`
- CreatedAt string `json:"created_at"`
-}
-
-type authtoken struct {
- ID string `json:"id"`
- URI string `json:"uri"`
- Description string `json:"description"`
- Metadata string `json:"metadata"`
- ACL []string `json:"acl"`
- OwnerID string `json:"owner_id"`
- CreatedAt string `json:"created_at"`
-}
-
-type sshCredential struct {
- ID string `json:"id"`
- URI string `json:"uri"`
- Description string `json:"description"`
- PublicKey string `json:"public_key"`
- Metadata string `json:"metadata"`
- ACL []string `json:"acl"`
- OwnerID string `json:"owner_id"`
- CreatedAt string `json:"created_at"`
-}
-
-type domain struct {
- ID string `json:"id"`
- URI string `json:"uri"`
- Domain string `json:"domain"`
- Metadata string `json:"metadata"`
- CreatedAt string `json:"created_at"`
-}
-
-type endpoint struct {
- ID string `json:"id"`
- Region string `json:"region"`
- Host string `json:"host"`
- Port int64 `json:"port"`
- PublicURL string `json:"public_url"`
- Proto string `json:"proto"`
- Hostport string `json:"hostport"`
- Type string `json:"type"`
- Bindings []string `json:"bindings"`
- URI string `json:"uri"`
- Metadata string `json:"metadata"`
- CreatedAt string `json:"created_at"`
- UpdatedAt string `json:"updated_at"`
-}
-
-type botUser struct {
- ID string `json:"id"`
- URI string `json:"uri"`
- Name string `json:"name"`
- Active bool `json:"active"`
- CreatedAt string `json:"created_at"`
-}
-
-type user struct {
- ID string `json:"id"`
-}
-
-type paginatedResponse struct {
- NextPageURI string `json:"next_page_uri"`
- APIKeys []apiKey `json:"keys,omitempty"`
- Authtokens []authtoken `json:"credentials,omitempty"`
- SSHCredentials []sshCredential `json:"ssh_credentials,omitempty"`
- Domains []domain `json:"reserved_domains,omitempty"`
- Endpoints []endpoint `json:"endpoints,omitempty"`
- BotUsers []botUser `json:"bot_users,omitempty"`
-}
-
-type secretInfo struct {
- Users []user
- BotUsers []botUser
- APIKeys []apiKey
- Authtokens []authtoken
- SSHCredentials []sshCredential
- Domains []domain
- Endpoints []endpoint
- AccountType AccountType
-}
diff --git a/pkg/analyzer/analyzers/ngrok/ngrok.go b/pkg/analyzer/analyzers/ngrok/ngrok.go
deleted file mode 100644
index 66abc4d638a5..000000000000
--- a/pkg/analyzer/analyzers/ngrok/ngrok.go
+++ /dev/null
@@ -1,292 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go ngrok
-package ngrok
-
-import (
- "errors"
- "fmt"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- _ "embed"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-type AccountType string
-
-const (
- AccountFree AccountType = "Free"
- AccountPaid AccountType = "Paid"
-)
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeNgrok
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, exist := credInfo["key"]
- if !exist {
- return nil, errors.New("key not found in credentials info")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Invalid Ngrok Key\n")
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- color.Green("[i] Valid Ngrok API Key\n")
- printAccountAndPermissions(info)
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*secretInfo, error) {
- // Ngrok API keys provide full access to all resources depending on the account type
- // Free accounts have access to a limited set of resources.
- client := analyzers.NewAnalyzeClient(cfg)
- secretInfo := &secretInfo{}
-
- if err := determineAccountType(client, secretInfo, key); err != nil {
- return nil, err
- }
-
- if err := populateAllResources(client, secretInfo, key); err != nil {
- return nil, err
- }
-
- return secretInfo, nil
-}
-
-func secretInfoToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- bindings := []analyzers.Binding{}
- fullAccessPermission := analyzers.Permission{
- Value: PermissionStrings[FullAccess],
- }
-
- for _, endpoint := range info.Endpoints {
- bindings = append(bindings, analyzers.Binding{
- Resource: createEndpointResource(endpoint),
- Permission: fullAccessPermission,
- })
- }
-
- for _, domain := range info.Domains {
- bindings = append(bindings, analyzers.Binding{
- Resource: createDomainResource(domain),
- Permission: fullAccessPermission,
- })
- }
-
- for _, apiKey := range info.APIKeys {
- bindings = append(bindings, analyzers.Binding{
- Resource: createAPIKeyResource(apiKey),
- Permission: fullAccessPermission,
- })
- }
-
- for _, authtoken := range info.Authtokens {
- bindings = append(bindings, analyzers.Binding{
- Resource: createAuthtokenResource(authtoken),
- Permission: fullAccessPermission,
- })
- }
-
- for _, sshCredential := range info.SSHCredentials {
- bindings = append(bindings, analyzers.Binding{
- Resource: createSSHKeyResource(sshCredential),
- Permission: fullAccessPermission,
- })
- }
-
- for _, botUser := range info.BotUsers {
- bindings = append(bindings, analyzers.Binding{
- Resource: createBotUserResource(botUser),
- Permission: fullAccessPermission,
- })
- }
-
- for _, user := range info.Users {
- bindings = append(bindings, analyzers.Binding{
- Resource: createUserResource(user),
- Permission: fullAccessPermission,
- })
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeNgrok,
- Metadata: nil,
- Bindings: bindings,
- UnboundedResources: []analyzers.Resource{{
- Name: "Account Plan",
- FullyQualifiedName: "account_plan/" + string(info.AccountType),
- Type: "account_plan",
- }},
- }
- return &result
-}
-
-func printAccountAndPermissions(info *secretInfo) {
- accountIsFree := info.AccountType != AccountPaid
- color.Yellow("[i] Account Type: %s", info.AccountType)
-
- color.Yellow("\n[i] Permissions:")
- t1 := table.NewWriter()
- t1.AppendHeader(table.Row{"Resource", "Access Level"})
-
- // Printing the access level to Ngrok resources
- for _, resource := range ngrokResources {
- accessLevel := "Full Access"
- if resource.IsPaidFeature && accountIsFree {
- accessLevel = "None"
- }
- t1.AppendRow(table.Row{
- color.GreenString(resource.Name),
- color.GreenString(accessLevel),
- })
- t1.AppendSeparator()
- }
-
- t1.SetOutputMirror(os.Stdout)
- t1.Render()
-
- color.Yellow("\n[i] Resources:")
-
- t2 := table.NewWriter()
- t2.SetTitle("User IDs")
- t2.AppendHeader(table.Row{"ID"})
-
- for _, user := range info.Users {
- t2.AppendRow(table.Row{
- color.GreenString(user.ID),
- })
- }
-
- t2.SetOutputMirror(os.Stdout)
- t2.Render()
-
- t3 := table.NewWriter()
- t3.SetTitle("Endpoints")
- t3.AppendHeader(table.Row{"ID", "Region", "Public URL", "Type", "Created At", "Updated At"})
- for _, endpoint := range info.Endpoints {
- t3.AppendRow(table.Row{
- color.GreenString(endpoint.ID),
- color.GreenString(endpoint.Region),
- color.GreenString(endpoint.PublicURL),
- color.GreenString(endpoint.Type),
- color.GreenString(endpoint.CreatedAt),
- color.GreenString(endpoint.UpdatedAt),
- })
- }
-
- t3.SetOutputMirror(os.Stdout)
- t3.Render()
-
- t4 := table.NewWriter()
- t4.SetTitle("Domains")
- t4.AppendHeader(table.Row{"ID", "Domain", "URI", "Created At"})
- for _, domain := range info.Domains {
- t4.AppendRow(table.Row{
- color.GreenString(domain.ID),
- color.GreenString(domain.Domain),
- color.GreenString(domain.URI),
- color.GreenString(domain.CreatedAt),
- })
- }
-
- t4.SetOutputMirror(os.Stdout)
- t4.Render()
-
- t5 := table.NewWriter()
- t5.SetTitle("API Keys")
- t5.AppendHeader(table.Row{"ID", "Description", "Owner ID", "Created At"})
- for _, key := range info.APIKeys {
- t5.AppendRow(table.Row{
- color.GreenString(key.ID),
- color.GreenString(key.Description),
- color.GreenString(key.OwnerID),
- color.GreenString(key.CreatedAt),
- })
- }
-
- t5.SetOutputMirror(os.Stdout)
- t5.Render()
-
- t6 := table.NewWriter()
- t6.SetTitle("Authtokens")
- t6.AppendHeader(table.Row{"ID", "Description", "Owner ID", "Created At"})
- for _, token := range info.Authtokens {
- t6.AppendRow(table.Row{
- color.GreenString(token.ID),
- color.GreenString(token.Description),
- color.GreenString(token.OwnerID),
- color.GreenString(token.CreatedAt),
- })
- }
-
- t6.SetOutputMirror(os.Stdout)
- t6.Render()
-
- t7 := table.NewWriter()
- t7.SetTitle("SSH Credentials")
- t7.AppendHeader(table.Row{"ID", "Description", "Owner ID", "Created At"})
- for _, key := range info.SSHCredentials {
- t7.AppendRow(table.Row{
- color.GreenString(key.ID),
- color.GreenString(key.Description),
- color.GreenString(key.OwnerID),
- color.GreenString(key.CreatedAt),
- })
- }
-
- t7.SetOutputMirror(os.Stdout)
- t7.Render()
-
- t8 := table.NewWriter()
- t8.SetTitle("Bot Users")
- t8.AppendHeader(table.Row{"ID", "Name", "Is Active", "Created At"})
- for _, endpoint := range info.BotUsers {
- isActive := "No"
- if endpoint.Active {
- isActive = "Yes"
- }
- t8.AppendRow(table.Row{
- color.GreenString(endpoint.ID),
- color.GreenString(endpoint.Name),
- color.GreenString(isActive),
- color.GreenString(endpoint.CreatedAt),
- })
- }
-
- t8.SetOutputMirror(os.Stdout)
- t8.Render()
-
- fmt.Printf("%s: https://www.ngrok.com/developers/documentation\n\n", color.GreenString("Ref"))
-}
diff --git a/pkg/analyzer/analyzers/ngrok/ngrok_test.go b/pkg/analyzer/analyzers/ngrok/ngrok_test.go
deleted file mode 100644
index 36f4001583be..000000000000
--- a/pkg/analyzer/analyzers/ngrok/ngrok_test.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package ngrok
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "analyzers1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- key := testSecrets.MustGetField("NGROK")
-
- tests := []struct {
- name string
- secret string
- want string
- wantErr bool
- }{
- {
- name: "valid ngrok credentials",
- secret: key,
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{
- "key": tt.secret,
- })
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Type == bindings[j].Resource.Type {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Type < bindings[j].Resource.Type
- })
-}
diff --git a/pkg/analyzer/analyzers/ngrok/permissions.go b/pkg/analyzer/analyzers/ngrok/permissions.go
deleted file mode 100644
index 2c3b7e882158..000000000000
--- a/pkg/analyzer/analyzers/ngrok/permissions.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package ngrok
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- FullAccess Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- FullAccess: "full_access",
- }
-
- StringToPermission = map[string]Permission{
- "full_access": FullAccess,
- }
-
- PermissionIDs = map[Permission]int{
- FullAccess: 1,
- }
-
- IdToPermission = map[int]Permission{
- 1: FullAccess,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/ngrok/permissions.yaml b/pkg/analyzer/analyzers/ngrok/permissions.yaml
deleted file mode 100644
index c17c8007f389..000000000000
--- a/pkg/analyzer/analyzers/ngrok/permissions.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-permissions:
- - full_access
diff --git a/pkg/analyzer/analyzers/ngrok/requests.go b/pkg/analyzer/analyzers/ngrok/requests.go
deleted file mode 100644
index 0c4d1c6575ab..000000000000
--- a/pkg/analyzer/analyzers/ngrok/requests.go
+++ /dev/null
@@ -1,374 +0,0 @@
-package ngrok
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
-)
-
-const (
- ngrokAPIBaseURL = "https://api.ngrok.com"
- reservedAddressesEndpoint = "/reserved_addrs"
- domainsEndpoint = "/reserved_domains"
- endpointsEndpoint = "/endpoints"
- apiKeysEndpoint = "/api_keys"
- sshCredentialsEndpoint = "/ssh_credentials"
- authtokensEndpoint = "/credentials"
- botUsersEndpoint = "/bot_users"
-)
-
-func determineAccountType(client *http.Client, info *secretInfo, key string) error {
- // To determine if the account is free or paid, we can attempt to create a reserved address
- // Reserved Addresses are only available to paid accounts, so if the response contains the
- // error "ERR_NGROK_501", we can assume the account is on a free plan.
- // Ref: https://ngrok.com/docs/errors/err_ngrok_501
-
- const errorCodeFreeAccount = "ERR_NGROK_501"
-
- url := fmt.Sprintf("%s%s", ngrokAPIBaseURL, reservedAddressesEndpoint)
- body, statusCode, err := makeAPIRequest(client, http.MethodPost, url, key)
- if err != nil {
- return err
- }
-
- // The response should be a 400 Bad Request based on our request. Any other status code indicates an error.
- if statusCode != http.StatusBadRequest {
- return fmt.Errorf("unexpected status code: %d while determining account type", statusCode)
- }
-
- switch statusCode {
- case http.StatusBadRequest:
- if strings.Contains(string(body), errorCodeFreeAccount) {
- info.AccountType = AccountFree
- } else {
- info.AccountType = AccountPaid
- }
- case http.StatusForbidden:
- return fmt.Errorf("invalid API key or access forbidden: %s", body)
- default:
- return fmt.Errorf("unexpected status code: %d while determining account type", statusCode)
- }
-
- return nil
-}
-
-func populateAllResources(client *http.Client, info *secretInfo, key string) error {
- // Fetch all resources and populate the secretInfo struct with the data
- // This is a placeholder function. The actual implementation will depend on the API endpoints and response formats.
- // For example, you might want to call different endpoints to fetch API keys, SSH keys, etc.
-
- // Example of populating API keys
- if err := populateEndpoints(client, info, key); err != nil {
- return err
- }
- if err := populateDomains(client, info, key); err != nil {
- return err
- }
- if err := populateAPIKeys(client, info, key); err != nil {
- return err
- }
- if err := populateAuthtokens(client, info, key); err != nil {
- return err
- }
- if err := populateSSHCredentials(client, info, key); err != nil {
- return err
- }
- if err := populateBotUsers(client, info, key); err != nil {
- return err
- }
- populateUsers(info)
-
- return nil
-}
-
-func populateEndpoints(client *http.Client, info *secretInfo, key string) error {
- url := fmt.Sprintf("%s%s", ngrokAPIBaseURL, endpointsEndpoint)
- info.Endpoints = []endpoint{}
- for {
- res, err := fetchResources(client, url, key)
- if err != nil {
- return err
- }
- info.Endpoints = append(info.Endpoints, res.Endpoints...)
- url = res.NextPageURI
- if url == "" {
- break
- }
- }
- return nil
-}
-
-func populateAPIKeys(client *http.Client, info *secretInfo, key string) error {
- url := fmt.Sprintf("%s%s", ngrokAPIBaseURL, apiKeysEndpoint)
- info.APIKeys = []apiKey{}
- for {
- res, err := fetchResources(client, url, key)
- if err != nil {
- return err
- }
- info.APIKeys = append(info.APIKeys, res.APIKeys...)
- url = res.NextPageURI
- if url == "" {
- break
- }
- }
- return nil
-}
-
-func populateSSHCredentials(client *http.Client, info *secretInfo, key string) error {
- url := fmt.Sprintf("%s%s", ngrokAPIBaseURL, sshCredentialsEndpoint)
- info.SSHCredentials = []sshCredential{}
- for {
- res, err := fetchResources(client, url, key)
- if err != nil {
- return err
- }
- info.SSHCredentials = append(info.SSHCredentials, res.SSHCredentials...)
- url = res.NextPageURI
- if url == "" {
- break
- }
- }
- return nil
-}
-
-func populateAuthtokens(client *http.Client, info *secretInfo, key string) error {
- url := fmt.Sprintf("%s%s", ngrokAPIBaseURL, authtokensEndpoint)
- info.Authtokens = []authtoken{}
- for {
- res, err := fetchResources(client, url, key)
- if err != nil {
- return err
- }
- info.Authtokens = append(info.Authtokens, res.Authtokens...)
- url = res.NextPageURI
- if url == "" {
- break
- }
- }
- return nil
-}
-
-func populateDomains(client *http.Client, info *secretInfo, key string) error {
- url := fmt.Sprintf("%s%s", ngrokAPIBaseURL, domainsEndpoint)
- info.Domains = []domain{}
- for {
- res, err := fetchResources(client, url, key)
- if err != nil {
- return err
- }
- info.Domains = append(info.Domains, res.Domains...)
- url = res.NextPageURI
- if url == "" {
- break
- }
- }
- return nil
-}
-
-func populateBotUsers(client *http.Client, info *secretInfo, key string) error {
- url := fmt.Sprintf("%s%s", ngrokAPIBaseURL, botUsersEndpoint)
- info.BotUsers = []botUser{}
- for {
- res, err := fetchResources(client, url, key)
- if err != nil {
- return err
- }
- info.BotUsers = append(info.BotUsers, res.BotUsers...)
- url = res.NextPageURI
- if url == "" {
- break
- }
- }
- return nil
-}
-
-func fetchResources(client *http.Client, url string, key string) (*paginatedResponse, error) {
- for {
- body, status, err := makeAPIRequest(client, http.MethodGet, url, key)
- if err != nil {
- return nil, err
- }
- switch status {
- case http.StatusOK:
- var resource paginatedResponse
- if err := json.Unmarshal(body, &resource); err != nil {
- return nil, err
- }
- return &resource, nil
- case http.StatusForbidden:
- return nil, fmt.Errorf("invalid API key or access forbidden: %s", body)
- default:
- return nil, fmt.Errorf("unexpected status code: %d", status)
- }
- }
-}
-
-func populateUsers(info *secretInfo) {
- // Creating a map to track unique user IDs to help in avoiding
- // duplicates when adding users to the info.Users slice
- uniqueUserIDs := map[string]bool{}
-
- processOwnerID := func(ownerID string) {
- if strings.HasPrefix(ownerID, "usr_") {
- if uniqueUserIDs[ownerID] {
- return
- }
- uniqueUserIDs[ownerID] = true
- info.Users = append(info.Users, user{ID: ownerID})
- }
- }
-
- for _, token := range info.Authtokens {
- processOwnerID(token.OwnerID)
- }
-
- for _, sshKey := range info.SSHCredentials {
- processOwnerID(sshKey.OwnerID)
- }
-
- for _, apiKey := range info.APIKeys {
- processOwnerID(apiKey.OwnerID)
- }
-}
-
-func makeAPIRequest(client *http.Client, method string, url string, key string) ([]byte, int, error) {
- var reqBody io.Reader = nil
- if method == http.MethodPost {
- reqBody = strings.NewReader("{}")
- }
- req, err := http.NewRequest(method, url, reqBody)
- if err != nil {
- return nil, 0, err
- }
- req.Header.Set("Authorization", "Bearer "+key)
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Ngrok-Version", "2")
- res, err := client.Do(req)
- if err != nil {
- return nil, 0, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return nil, 0, fmt.Errorf("failed to read response body: %w", err)
- }
-
- return bodyBytes, res.StatusCode, nil
-}
-
-// Functions to create analyzers.Resource objects for different resource types
-
-func createEndpointResource(endpoint endpoint) analyzers.Resource {
- return analyzers.Resource{
- Name: endpoint.ID,
- FullyQualifiedName: "endpoint/" + endpoint.ID,
- Type: "endpoint",
- Metadata: map[string]any{
- "region": endpoint.Region,
- "host": endpoint.Host,
- "port": endpoint.Port,
- "publicURL": endpoint.PublicURL,
- "proto": endpoint.Proto,
- "hostport": endpoint.Hostport,
- "type": endpoint.Type,
- "uri": endpoint.URI,
- "bindings": endpoint.Bindings,
- "metadata": endpoint.Metadata,
- "createdAt": endpoint.CreatedAt,
- "updatedAt": endpoint.UpdatedAt,
- },
- }
-}
-
-func createDomainResource(domain domain) analyzers.Resource {
- return analyzers.Resource{
- Name: domain.ID,
- FullyQualifiedName: "domain/" + domain.ID,
- Type: "domain",
- Metadata: map[string]any{
- "uri": domain.URI,
- "domain": domain.Domain,
- "metadata": domain.Metadata,
- "createdAt": domain.CreatedAt,
- },
- }
-}
-
-func createAPIKeyResource(apiKey apiKey) analyzers.Resource {
- return analyzers.Resource{
- Name: apiKey.ID,
- FullyQualifiedName: "api_key/" + apiKey.ID,
- Type: "api_key",
- Metadata: map[string]any{
- "uri": apiKey.URI,
- "description": apiKey.Description,
- "metadata": apiKey.Metadata,
- "ownerID": apiKey.OwnerID,
- "createdAt": apiKey.CreatedAt,
- },
- }
-}
-func createSSHKeyResource(sshCredential sshCredential) analyzers.Resource {
- return analyzers.Resource{
- Name: sshCredential.ID,
- FullyQualifiedName: "ssh_credential/" + sshCredential.ID,
- Type: "ssh_credential",
- Metadata: map[string]any{
- "uri": sshCredential.URI,
- "description": sshCredential.Description,
- "publicKey": sshCredential.PublicKey,
- "metadata": sshCredential.Metadata,
- "acl": sshCredential.ACL,
- "ownerID": sshCredential.OwnerID,
- "createdAt": sshCredential.CreatedAt,
- },
- }
-}
-
-func createAuthtokenResource(authtoken authtoken) analyzers.Resource {
- return analyzers.Resource{
- Name: authtoken.ID,
- FullyQualifiedName: "authtoken/" + authtoken.ID,
- Type: "authtoken",
- Metadata: map[string]any{
- "uri": authtoken.URI,
- "description": authtoken.Description,
- "metadata": authtoken.Metadata,
- "acl": authtoken.ACL,
- "ownerID": authtoken.OwnerID,
- "createdAt": authtoken.CreatedAt,
- },
- }
-}
-
-func createBotUserResource(botUser botUser) analyzers.Resource {
- return analyzers.Resource{
- Name: botUser.ID,
- FullyQualifiedName: "bot_user/" + botUser.ID,
- Type: "bot_user",
- Metadata: map[string]any{
- "uri": botUser.URI,
- "name": botUser.Name,
- "active": botUser.Active,
- "createdAt": botUser.CreatedAt,
- },
- }
-}
-
-func createUserResource(user user) analyzers.Resource {
- return analyzers.Resource{
- Name: user.ID,
- FullyQualifiedName: "user/" + user.ID,
- Type: "user",
- }
-}
diff --git a/pkg/analyzer/analyzers/ngrok/resources.go b/pkg/analyzer/analyzers/ngrok/resources.go
deleted file mode 100644
index 0930aab58c1b..000000000000
--- a/pkg/analyzer/analyzers/ngrok/resources.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package ngrok
-
-type ngrokResource struct {
- Name string
- IsPaidFeature bool
-}
-
-var ngrokResources = []ngrokResource{
- {
- Name: "Endpoints",
- IsPaidFeature: false,
- },
- {
- Name: "Domains",
- IsPaidFeature: false,
- },
- {
- Name: "Reserved Addresses",
- IsPaidFeature: true,
- },
- {
- Name: "TLS Certificates",
- IsPaidFeature: true,
- },
- {
- Name: "Kubernetes Operators",
- IsPaidFeature: true,
- },
- {
- Name: "Certificate Authorities",
- IsPaidFeature: true,
- },
- {
- Name: "IP Policies",
- IsPaidFeature: true,
- },
- {
- Name: "Policy Rules",
- IsPaidFeature: true,
- },
- {
- Name: "Application Users",
- IsPaidFeature: true,
- },
- {
- Name: "Application Sessions",
- IsPaidFeature: true,
- },
- {
- Name: "Agent Ingress",
- IsPaidFeature: true,
- },
- {
- Name: "Tunnels",
- IsPaidFeature: false,
- },
- {
- Name: "Tunnel Sessions",
- IsPaidFeature: false,
- },
- {
- Name: "Event Destinations",
- IsPaidFeature: false,
- },
- {
- Name: "Event Sources",
- IsPaidFeature: false,
- },
- {
- Name: "Event Subscriptions",
- IsPaidFeature: false,
- },
- {
- Name: "IP Restrictions",
- IsPaidFeature: true,
- },
- {
- Name: "API Keys",
- IsPaidFeature: false,
- },
- {
- Name: "SSH Credentials",
- IsPaidFeature: false,
- },
- {
- Name: "Authtokens",
- IsPaidFeature: false,
- },
- {
- Name: "Bot Users",
- IsPaidFeature: false,
- },
- {
- Name: "SSH Certificate Authorities",
- IsPaidFeature: true,
- },
- {
- Name: "SSH Host Certificates",
- IsPaidFeature: true,
- },
- {
- Name: "SSH User Certificates",
- IsPaidFeature: true,
- },
-}
diff --git a/pkg/analyzer/analyzers/notion/expected_output.json b/pkg/analyzer/analyzers/notion/expected_output.json
deleted file mode 100644
index 203535ec53cb..000000000000
--- a/pkg/analyzer/analyzers/notion/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":22,"Bindings":[{"Resource":{"Name":"hooman","FullyQualifiedName":"notion.so/bot/62faeec3-a948-4dd4-90ae-426e0b192902","Type":"bot","Metadata":{"workspace":"hoomanit"},"Parent":null},"Permission":{"Value":"insert_content","Parent":null}},{"Resource":{"Name":"hooman","FullyQualifiedName":"notion.so/bot/62faeec3-a948-4dd4-90ae-426e0b192902","Type":"bot","Metadata":{"workspace":"hoomanit"},"Parent":null},"Permission":{"Value":"read_content","Parent":null}},{"Resource":{"Name":"hooman","FullyQualifiedName":"notion.so/bot/62faeec3-a948-4dd4-90ae-426e0b192902","Type":"bot","Metadata":{"workspace":"hoomanit"},"Parent":null},"Permission":{"Value":"read_users_with_email","Parent":null}},{"Resource":{"Name":"hooman","FullyQualifiedName":"notion.so/bot/62faeec3-a948-4dd4-90ae-426e0b192902","Type":"bot","Metadata":{"workspace":"hoomanit"},"Parent":null},"Permission":{"Value":"update_content","Parent":null}}],"UnboundedResources":[{"Name":"hooman","FullyQualifiedName":"notion.so/person/3d0600fa-fa18-427d-8abc-58b662f0d209","Type":"person","Metadata":{"email":"rendyplayground@gmail.com"},"Parent":null}],"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/notion/notion.go b/pkg/analyzer/analyzers/notion/notion.go
deleted file mode 100644
index 7a70a42e91d4..000000000000
--- a/pkg/analyzer/analyzers/notion/notion.go
+++ /dev/null
@@ -1,389 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go notion
-
-package notion
-
-import (
- "bytes"
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeNotion }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("missing key in credInfo")
- }
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeNotion,
- Metadata: nil,
- Bindings: make([]analyzers.Binding, len(info.Permissions)),
- UnboundedResources: make([]analyzers.Resource, 0, len(info.WorkspaceUsers)),
- }
-
- resource := analyzers.Resource{
- Name: info.Bot.Name,
- FullyQualifiedName: "notion.so/bot/" + info.Bot.Id,
- Type: info.Bot.Type,
- Metadata: map[string]interface{}{
- "workspace": info.Bot.GetWorkspaceName(),
- },
- }
-
- for idx, permission := range info.Permissions {
- result.Bindings[idx] = analyzers.Binding{
- Resource: resource,
- Permission: analyzers.Permission{
- Value: permission,
- },
- }
- }
-
- // We can find list of users in the current workspace
- // if the API key has read_user permission, so these can be
- // unbounded resources
- for _, user := range info.WorkspaceUsers {
- if info.Bot.Id == user.Id {
- // Skip the bot itself
- continue
- }
- unboundresource := analyzers.Resource{
- Name: user.Name,
- FullyQualifiedName: fmt.Sprintf("notion.so/%s/%s", user.Type, user.Id),
- Type: user.Type, // person or bot
- }
- if user.Person.Email != "" {
- unboundresource.Metadata = map[string]interface{}{
- "email": user.Person.Email,
- }
- }
-
- result.UnboundedResources = append(result.UnboundedResources, unboundresource)
- }
-
- return &result
-}
-
-//go:embed scopes.json
-var scopesConfig []byte
-
-type HttpStatusTest struct {
- Endpoint string `json:"endpoint"`
- Method string `json:"method"`
- Payload interface{} `json:"payload"`
- ValidStatuses []int `json:"valid_status_code"`
- InvalidStatuses []int `json:"invalid_status_code"`
-}
-
-func StatusContains(status int, vals []int) bool {
- for _, v := range vals {
- if status == v {
- return true
- }
- }
- return false
-}
-
-func (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string) (bool, error) {
- // If body data, marshal to JSON
- var data io.Reader
- if h.Payload != nil {
- jsonData, err := json.Marshal(h.Payload)
- if err != nil {
- return false, err
- }
- data = bytes.NewBuffer(jsonData)
- }
-
- // Create new HTTP request
- client := analyzers.NewAnalyzeClientUnrestricted(cfg)
- req, err := http.NewRequest(h.Method, h.Endpoint, data)
- if err != nil {
- return false, err
- }
-
- // Add custom headers if provided
- for key, value := range headers {
- req.Header.Set(key, value)
- }
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer resp.Body.Close()
-
- // Check response status code
- switch {
- case StatusContains(resp.StatusCode, h.ValidStatuses):
- return true, nil
- case StatusContains(resp.StatusCode, h.InvalidStatuses):
- return false, nil
- default:
- return false, errors.New("error checking response status code")
- }
-}
-
-type Scope struct {
- Name string `json:"name"`
- HttpTest HttpStatusTest `json:"test"`
-}
-
-func readInScopes() ([]Scope, error) {
- var scopes []Scope
- if err := json.Unmarshal(scopesConfig, &scopes); err != nil {
- return nil, err
- }
-
- return scopes, nil
-}
-
-func getPermissions(cfg *config.Config, key string) ([]string, error) {
- scopes, err := readInScopes()
- if err != nil {
- return nil, fmt.Errorf("reading in scopes: %w", err)
- }
-
- permissions := make([]string, 0, len(scopes))
- for _, scope := range scopes {
- status, err := scope.HttpTest.RunTest(cfg, map[string]string{"Authorization": "Bearer " + key, "Notion-Version": "2022-06-28"})
- if err != nil {
- return nil, fmt.Errorf("running test: %w", err)
- }
- if status {
- permissions = append(permissions, scope.Name)
- }
- }
-
- return permissions, nil
-}
-
-type SecretInfo struct {
- Bot *bot
- WorkspaceUsers []user
- Permissions []string
-}
-
-type user struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"`
- Person struct {
- Email string `json:"email"`
- }
-}
-
-type bot struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"`
- Bot struct {
- Owner *struct {
- Type string `json:"type"`
- }
- WorkspaceName string `json:"workspace_name"`
- } `json:"bot"`
-}
-
-func (b *bot) GetWorkspaceName() string {
- return b.Bot.WorkspaceName
-}
-
-func (b *bot) OwnedBy() string {
- if b.Bot.Owner != nil {
- return b.Bot.Owner.Type
- }
- return "N/A"
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- color.Green("[!] Valid Notion API key\n\n")
-
- color.Green("[i] Bot: %s (%s)\n", info.Bot.Name, info.Bot.Id)
- color.Green("[i] Bot Owned By: %s\n", info.Bot.OwnedBy())
-
- if info.Bot.GetWorkspaceName() != "" {
- color.Green("[i] Workspace: %s\n\n", info.Bot.GetWorkspaceName())
- }
-
- printPermissions(info.Permissions)
- if len(info.WorkspaceUsers) > 0 {
- printUsers(info.WorkspaceUsers)
- }
- color.Yellow("\n[i] Expires: Never")
-
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- permissions := make([]string, 0)
-
- client := analyzers.NewAnalyzeClient(cfg)
-
- bot, err := getBotInfo(client, key)
- if err != nil {
- return nil, err
- }
-
- credPermissions, err := getPermissions(cfg, key)
- if err != nil {
- return nil, err
- }
-
- permissions = append(permissions, credPermissions...)
-
- users, err := getWorkspaceUsers(client, key)
- if err != nil {
- return nil, fmt.Errorf("error getting user permission: %s", err.Error())
- }
-
- // check if email is returned in users to determine permission
- for _, user := range users {
- if user.Type == "person" {
- if user.Person.Email == "" {
- permissions = append(permissions, PermissionStrings[ReadUsersWithoutEmail])
- } else {
- permissions = append(permissions, PermissionStrings[ReadUsersWithEmail])
- }
- break
- }
- }
- return &SecretInfo{
- Bot: bot,
- Permissions: permissions,
- WorkspaceUsers: users,
- }, nil
-}
-
-func printPermissions(permissions []string) {
- color.Yellow("[i] Permissions:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission"})
- for _, permission := range permissions {
- t.AppendRow(table.Row{color.GreenString(permission)})
- }
- t.Render()
-}
-
-func printUsers(users []user) {
- color.Yellow("\n[i] Workspace Users:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"ID", "Name", "Type", "Email"})
- for _, user := range users {
- t.AppendRow(table.Row{color.GreenString(user.Id), color.GreenString(user.Name), color.GreenString(user.Type), color.GreenString(user.Person.Email)})
- }
- t.Render()
-}
-
-func getBotInfo(client *http.Client, key string) (*bot, error) {
- // Create new HTTP request
- req, err := http.NewRequest(http.MethodGet, "https://api.notion.com/v1/users/me", http.NoBody)
- if err != nil {
- return nil, err
- }
-
- // Add custom headers if provided
- req.Header.Set("Authorization", "Bearer "+key)
- req.Header.Set("Notion-Version", "2022-06-28")
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- switch resp.StatusCode {
- case http.StatusOK:
- me := &bot{}
- err = json.NewDecoder(resp.Body).Decode(me)
- if err != nil {
- return nil, err
- }
- return me, nil
- case http.StatusUnauthorized:
- return nil, errors.New("invalid API key")
- default:
- return nil, errors.New("error getting bot info")
- }
-}
-
-// Decode response body
-type usersResponse struct {
- Results []user `json:"results"`
-}
-
-func getWorkspaceUsers(client *http.Client, key string) ([]user, error) {
- // Create new HTTP request
- req, err := http.NewRequest(http.MethodGet, "https://api.notion.com/v1/users", http.NoBody)
- if err != nil {
- return nil, err
- }
-
- // Add custom headers if provided
- req.Header.Set("Authorization", "Bearer "+key)
- req.Header.Set("Notion-Version", "2022-06-28")
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- switch resp.StatusCode {
- case http.StatusOK:
- response := &usersResponse{}
- err = json.NewDecoder(resp.Body).Decode(response)
- if err != nil {
- return nil, err
- }
- return response.Results, nil
- case http.StatusUnauthorized:
- return nil, errors.New("invalid API key")
- case http.StatusForbidden:
- return nil, nil // no permission
- case http.StatusNotFound:
- return nil, errors.New("workspace not found")
- default:
- return nil, errors.New("error checking user permissions")
- }
-
-}
diff --git a/pkg/analyzer/analyzers/notion/notion_test.go b/pkg/analyzer/analyzers/notion/notion_test.go
deleted file mode 100644
index f6d92eaabae1..000000000000
--- a/pkg/analyzer/analyzers/notion/notion_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package notion
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid notion key",
- key: testSecrets.MustGetField("NOTION_TOKEN"),
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/notion/permissions.go b/pkg/analyzer/analyzers/notion/permissions.go
deleted file mode 100644
index deb0259665cc..000000000000
--- a/pkg/analyzer/analyzers/notion/permissions.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package notion
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- ReadContent Permission = iota
- UpdateContent Permission = iota
- InsertContent Permission = iota
- ReadComments Permission = iota
- InsertComments Permission = iota
- ReadUsersWithEmail Permission = iota
- ReadUsersWithoutEmail Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- ReadContent: "read_content",
- UpdateContent: "update_content",
- InsertContent: "insert_content",
- ReadComments: "read_comments",
- InsertComments: "insert_comments",
- ReadUsersWithEmail: "read_users_with_email",
- ReadUsersWithoutEmail: "read_users_without_email",
- }
-
- StringToPermission = map[string]Permission{
- "read_content": ReadContent,
- "update_content": UpdateContent,
- "insert_content": InsertContent,
- "read_comments": ReadComments,
- "insert_comments": InsertComments,
- "read_users_with_email": ReadUsersWithEmail,
- "read_users_without_email": ReadUsersWithoutEmail,
- }
-
- PermissionIDs = map[Permission]int{
- ReadContent: 1,
- UpdateContent: 2,
- InsertContent: 3,
- ReadComments: 4,
- InsertComments: 5,
- ReadUsersWithEmail: 6,
- ReadUsersWithoutEmail: 7,
- }
-
- IdToPermission = map[int]Permission{
- 1: ReadContent,
- 2: UpdateContent,
- 3: InsertContent,
- 4: ReadComments,
- 5: InsertComments,
- 6: ReadUsersWithEmail,
- 7: ReadUsersWithoutEmail,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/notion/permissions.yaml b/pkg/analyzer/analyzers/notion/permissions.yaml
deleted file mode 100644
index 269a1b0f6fe3..000000000000
--- a/pkg/analyzer/analyzers/notion/permissions.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-permissions:
- - read_content
- - update_content
- - insert_content
- - read_comments
- - insert_comments
- - read_users_with_email
- - read_users_without_email
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/notion/scopes.json b/pkg/analyzer/analyzers/notion/scopes.json
deleted file mode 100644
index 25f645dc5808..000000000000
--- a/pkg/analyzer/analyzers/notion/scopes.json
+++ /dev/null
@@ -1,47 +0,0 @@
-[
- {
- "name": "read_content",
- "test": {
- "endpoint": "https://api.notion.com/v1/pages/`nowaythiscanexist",
- "method": "GET",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "update_content",
- "test": {
- "endpoint": "https://api.notion.com/v1/pages/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "insert_content",
- "test": {
- "endpoint": "https://api.notion.com/v1/pages",
- "method": "POST",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "read_comments",
- "test": {
- "endpoint": "https://api.notion.com/v1/comments",
- "method": "GET",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "insert_comments",
- "test": {
- "endpoint": "https://api.notion.com/v1/comments",
- "method": "POST",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- }
-]
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/openai/openai.go b/pkg/analyzer/analyzers/openai/openai.go
deleted file mode 100644
index 41103dcd8035..000000000000
--- a/pkg/analyzer/analyzers/openai/openai.go
+++ /dev/null
@@ -1,365 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go openai
-
-package openai
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "os"
- "strconv"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeOpenAI }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- info, err := AnalyzePermissions(a.Cfg, credInfo["key"])
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *AnalyzerJSON) *analyzers.AnalyzerResult {
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeOpenAI,
- Metadata: map[string]any{
- "user": info.me.Name,
- "email": info.me.Email,
- "mfa": strconv.FormatBool(info.me.MfaEnabled),
- "is_admin": strconv.FormatBool(info.isAdmin),
- "is_restricted": strconv.FormatBool(info.isRestricted),
- },
- }
-
- perms := convertPermissions(info.isAdmin, info.perms)
- for _, org := range info.me.Orgs.Data {
- resource := analyzers.Resource{
- Name: org.Title,
- FullyQualifiedName: org.ID,
- Type: "organization",
- Metadata: map[string]any{
- "description": org.Description,
- "user": org.User,
- },
- }
- // Copy each permission into this resource.
- result.Bindings = append(result.Bindings, analyzers.BindAllPermissions(resource, perms...)...)
- }
-
- return &result
-}
-
-func convertPermissions(isAdmin bool, perms []permissionData) []analyzers.Permission {
- var permissions []analyzers.Permission
-
- if isAdmin {
- permissions = append(permissions, analyzers.Permission{Value: analyzers.FullAccess})
- } else {
- for _, perm := range flattenPerms(perms...) {
- permName := PermissionStrings[perm]
- permissions = append(permissions, analyzers.Permission{Value: permName})
- }
- }
-
- return permissions
-}
-
-// flattenPerms takes a slice of permissionData and returns all of the
-// individual Permission values in a single slice.
-func flattenPerms(perms ...permissionData) []Permission {
- var output []Permission
- for _, perm := range perms {
- output = append(output, perm.permissions...)
- }
- return output
-}
-
-const (
- BASE_URL = "https://api.openai.com"
- ORGS_ENDPOINT = "/v1/organizations"
- ME_ENDPOINT = "/v1/me"
-)
-
-type MeJSON struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Email string `json:"email"`
- Phone string `json:"phone_number"`
- MfaEnabled bool `json:"mfa_flag_enabled"`
- Orgs struct {
- Data []struct {
- ID string `json:"id"`
- Title string `json:"title"`
- User string `json:"name"`
- Description string `json:"description"`
- Personal bool `json:"personal"`
- Default bool `json:"is_default"`
- Role string `json:"role"`
- } `json:"data"`
- } `json:"orgs"`
-}
-
-type permissionData struct {
- name string
- endpoints []string
- status analyzers.PermissionType
- permissions []Permission
-}
-
-type AnalyzerJSON struct {
- me MeJSON
- isAdmin bool
- isRestricted bool
- perms []permissionData
-}
-
-var POST_PAYLOAD = map[string]interface{}{"speed": 1}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, apiKey string) {
- data, err := AnalyzePermissions(cfg, apiKey)
- if err != nil {
- color.Red("[x] %s", err.Error())
- return
- }
- color.Green("[!] Valid OpenAI Token\n\n")
-
- printAPIKeyType(apiKey)
- printData(data.me)
-
- if data.isAdmin {
- color.Green("[!] Admin API Key. All permissions available.")
- } else if data.isRestricted {
- color.Yellow("[!] Restricted API Key. Limited permissions available.")
- printPermissions(data.perms, cfg.ShowAll)
- }
-}
-
-// AnalyzePermissions will analyze the permissions of an OpenAI API key
-func AnalyzePermissions(cfg *config.Config, key string) (*AnalyzerJSON, error) {
- data := AnalyzerJSON{
- isAdmin: false,
- isRestricted: false,
- }
-
- meJSON, err := getUserData(cfg, key)
- if err != nil {
- return nil, err
- }
- data.me = meJSON
-
- isAdmin, err := checkAdminKey(cfg, key)
- if err != nil {
- return nil, err
- }
-
- if isAdmin {
- data.isAdmin = true
- } else {
- data.isRestricted = true
- if err := analyzeScopes(key); err != nil {
- return nil, err
- }
- data.perms = getPermissions()
- }
-
- return &data, nil
-}
-
-func analyzeScopes(key string) error {
- for _, scope := range SCOPES {
- if err := scope.RunTests(key); err != nil {
- return err
- }
- }
- return nil
-}
-
-func openAIRequest(cfg *config.Config, method string, url string, key string, data map[string]interface{}) ([]byte, *http.Response, error) {
- var inBody io.Reader
- if data != nil {
- jsonData, err := json.Marshal(data)
- if err != nil {
- return nil, nil, err
- }
- inBody = bytes.NewBuffer(jsonData)
- }
-
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest(method, url, inBody)
- if err != nil {
- return nil, nil, err
- }
- req.Header.Add("Authorization", "Bearer "+key)
- req.Header.Add("Content-Type", "application/json")
- resp, err := client.Do(req)
- if err != nil {
- return nil, nil, err
- }
-
- defer resp.Body.Close()
-
- outBody, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, nil, err
- }
-
- return outBody, resp, nil
-}
-
-func checkAdminKey(cfg *config.Config, key string) (bool, error) {
- // Check for all permissions
- //nolint:bodyclose
- _, resp, err := openAIRequest(cfg, "GET", BASE_URL+ORGS_ENDPOINT, key, nil)
- if err != nil {
- return false, err
- }
- switch resp.StatusCode {
- case 200:
- return true, nil
- case 403:
- return false, nil
- default:
- return false, err
- }
-}
-
-func getUserData(cfg *config.Config, key string) (MeJSON, error) {
- var meJSON MeJSON
- //nolint:bodyclose
- me, resp, err := openAIRequest(cfg, "GET", BASE_URL+ME_ENDPOINT, key, nil)
- if err != nil {
- return meJSON, err
- }
-
- if resp.StatusCode != 200 {
- return meJSON, fmt.Errorf("invalid OpenAI token")
- }
-
- // Marshall me into meJSON struct
- if err := json.Unmarshal(me, &meJSON); err != nil {
- return meJSON, err
- }
- return meJSON, nil
-}
-
-func printAPIKeyType(apiKey string) {
- if strings.Contains(apiKey, "-svcacct-") {
- color.Yellow("[i] Service Account API Key\n")
- } else if strings.Contains(apiKey, "-admin-") {
- color.Yellow("[i] Admin API Key\n")
- } else {
- color.Yellow("[i] Project/Org API Key\n")
- }
-}
-func printData(meJSON MeJSON) {
- if meJSON.Name != "" && meJSON.Email != "" {
- userTable := table.NewWriter()
- userTable.SetOutputMirror(os.Stdout)
- color.Green("[i] User Information")
- userTable.AppendHeader(table.Row{"UserID", "User", "Email", "Phone", "MFA Enabled"})
- userTable.AppendRow(table.Row{meJSON.ID, meJSON.Name, meJSON.Email, meJSON.Phone, meJSON.MfaEnabled})
- userTable.Render()
- } else {
- color.Yellow("[!] No User Information Available")
- }
-
- if len(meJSON.Orgs.Data) > 0 {
- orgTable := table.NewWriter()
- orgTable.SetOutputMirror(os.Stdout)
- color.Green("[i] Organizations Information")
- orgTable.AppendHeader(table.Row{"Org ID", "Title", "User", "Default", "Role"})
- for _, org := range meJSON.Orgs.Data {
- orgTable.AppendRow(table.Row{org.ID, fmt.Sprintf("%s (%s)", org.Title, org.Description), org.User, org.Default, org.Role})
- }
- orgTable.Render()
- } else {
- color.Yellow("[!] No Organizations Information Available")
- }
-}
-
-func stringifyPermissionStatus(scope OpenAIScope) ([]Permission, analyzers.PermissionType) {
- readStatus := false
- writeStatus := false
- errors := false
- for _, test := range scope.ReadTests {
- if test.Type == analyzers.READ {
- readStatus = test.Status.Value
- }
- if test.Status.IsError {
- errors = true
- }
- }
- for _, test := range scope.WriteTests {
- if test.Type == analyzers.WRITE {
- writeStatus = test.Status.Value
- }
- if test.Status.IsError {
- errors = true
- }
- }
- if errors {
- return nil, analyzers.ERROR
- }
- if readStatus && writeStatus {
- return []Permission{scope.ReadPermission, scope.WritePermission}, analyzers.READ_WRITE
- } else if readStatus {
- return []Permission{scope.ReadPermission}, analyzers.READ
- } else if writeStatus {
- return []Permission{scope.WritePermission}, analyzers.WRITE
- } else {
- return nil, analyzers.NONE
- }
-}
-
-func getPermissions() []permissionData {
- var perms []permissionData
-
- for _, scope := range SCOPES {
- permissions, status := stringifyPermissionStatus(scope)
- perms = append(perms, permissionData{
- name: scope.Endpoints[0], // Using the first endpoint as the name for simplicity
- endpoints: scope.Endpoints,
- status: status,
- permissions: permissions,
- })
- }
-
- return perms
-}
-
-func printPermissions(perms []permissionData, showAll bool) {
- fmt.Print("\n\n")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Scope", "Endpoints", "Permission"})
-
- for _, perm := range perms {
- if showAll || perm.status != analyzers.NONE {
- t.AppendRow([]any{perm.name, perm.endpoints[0], perm.status})
-
- for i := 1; i < len(perm.endpoints); i++ {
- t.AppendRow([]any{"", perm.endpoints[i], perm.status})
- }
- }
- }
-
- t.Render()
- fmt.Print("\n\n")
-}
diff --git a/pkg/analyzer/analyzers/openai/openai_test.go b/pkg/analyzer/analyzers/openai/openai_test.go
deleted file mode 100644
index 36ebaf2ce1cf..000000000000
--- a/pkg/analyzer/analyzers/openai/openai_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package openai
-
-import (
- _ "embed"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed result_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want []byte
- wantErr bool
- }{
- {
- name: "valid OpenAI key",
- key: testSecrets.MustGetField("OPENAI_VERIFIED"),
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/openai/permissions.go b/pkg/analyzer/analyzers/openai/permissions.go
deleted file mode 100644
index 730f776c81e9..000000000000
--- a/pkg/analyzer/analyzers/openai/permissions.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package openai
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- ModelsRead Permission = iota
- ModelCapabilitiesWrite Permission = iota
- AssistantsRead Permission = iota
- AssistantsWrite Permission = iota
- ThreadsRead Permission = iota
- ThreadsWrite Permission = iota
- FineTuningRead Permission = iota
- FineTuningWrite Permission = iota
- FilesRead Permission = iota
- FilesWrite Permission = iota
- EvalsRead Permission = iota
- EvalsWrite Permission = iota
- ResponsesRead Permission = iota
- ResponsesWrite Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- ModelsRead: "models:read",
- ModelCapabilitiesWrite: "model_capabilities:write",
- AssistantsRead: "assistants:read",
- AssistantsWrite: "assistants:write",
- ThreadsRead: "threads:read",
- ThreadsWrite: "threads:write",
- FineTuningRead: "fine_tuning:read",
- FineTuningWrite: "fine_tuning:write",
- FilesRead: "files:read",
- FilesWrite: "files:write",
- EvalsRead: "evals:read",
- EvalsWrite: "evals:write",
- ResponsesRead: "responses:read",
- ResponsesWrite: "responses:write",
- }
-
- StringToPermission = map[string]Permission{
- "models:read": ModelsRead,
- "model_capabilities:write": ModelCapabilitiesWrite,
- "assistants:read": AssistantsRead,
- "assistants:write": AssistantsWrite,
- "threads:read": ThreadsRead,
- "threads:write": ThreadsWrite,
- "fine_tuning:read": FineTuningRead,
- "fine_tuning:write": FineTuningWrite,
- "files:read": FilesRead,
- "files:write": FilesWrite,
- "evals:read": EvalsRead,
- "evals:write": EvalsWrite,
- "responses:read": ResponsesRead,
- "responses:write": ResponsesWrite,
- }
-
- PermissionIDs = map[Permission]int{
- ModelsRead: 1,
- ModelCapabilitiesWrite: 2,
- AssistantsRead: 3,
- AssistantsWrite: 4,
- ThreadsRead: 5,
- ThreadsWrite: 6,
- FineTuningRead: 7,
- FineTuningWrite: 8,
- FilesRead: 9,
- FilesWrite: 10,
- EvalsRead: 11,
- EvalsWrite: 12,
- ResponsesRead: 13,
- ResponsesWrite: 14,
- }
-
- IdToPermission = map[int]Permission{
- 1: ModelsRead,
- 2: ModelCapabilitiesWrite,
- 3: AssistantsRead,
- 4: AssistantsWrite,
- 5: ThreadsRead,
- 6: ThreadsWrite,
- 7: FineTuningRead,
- 8: FineTuningWrite,
- 9: FilesRead,
- 10: FilesWrite,
- 11: EvalsRead,
- 12: EvalsWrite,
- 13: ResponsesRead,
- 14: ResponsesWrite,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/openai/permissions.yaml b/pkg/analyzer/analyzers/openai/permissions.yaml
deleted file mode 100644
index 56a6d52204d4..000000000000
--- a/pkg/analyzer/analyzers/openai/permissions.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-permissions:
-- models:read
-- model_capabilities:write
-- assistants:read
-- assistants:write
-- threads:read
-- threads:write
-- fine_tuning:read
-- fine_tuning:write
-- files:read
-- files:write
-- evals:read
-- evals:write
-- responses:read
-- responses:write
diff --git a/pkg/analyzer/analyzers/openai/result_output.json b/pkg/analyzer/analyzers/openai/result_output.json
deleted file mode 100644
index c667284f39a4..000000000000
--- a/pkg/analyzer/analyzers/openai/result_output.json
+++ /dev/null
@@ -1,461 +0,0 @@
-{
- "AnalyzerType": 13,
- "Bindings": [
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "models:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "model_capabilities:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assistants:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assistants:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "threads:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "threads:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "fine_tuning:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "fine_tuning:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "files:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "files:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "evals:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "evals:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "responses:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Truffle Security Co",
- "FullyQualifiedName": "org-n56tuYdSewh06PEGJZC0xWHf",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "truffle-security-co"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "responses:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "models:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "model_capabilities:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assistants:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "assistants:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "threads:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "threads:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "fine_tuning:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "fine_tuning:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "files:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "files:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "evals:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "evals:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "responses:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Personal",
- "FullyQualifiedName": "org-S2T2qOGM1KofMLUxb9rt7eV0",
- "Type": "organization",
- "Metadata": {
- "description": "Personal org for dustin@trufflesec.com",
- "user": "user-ohfap0ky8lkatw97iskuhghv"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "responses:write",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {
- "email": "dustin@trufflesec.com",
- "is_admin": "false",
- "is_restricted": "true",
- "mfa": "true",
- "user": "Dustin Decker"
- }
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/openai/scopes.go b/pkg/analyzer/analyzers/openai/scopes.go
deleted file mode 100644
index 36ca9e53810b..000000000000
--- a/pkg/analyzer/analyzers/openai/scopes.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package openai
-
-import (
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
-)
-
-type OpenAIScope struct {
- ReadTests []analyzers.HttpStatusTest
- WriteTests []analyzers.HttpStatusTest
- Endpoints []string
- ReadPermission Permission
- WritePermission Permission
-}
-
-func (s *OpenAIScope) RunTests(key string) error {
- headers := map[string]string{
- "Authorization": "Bearer " + key,
- "Content-Type": "application/json",
- }
- for i := range s.ReadTests {
- test := &s.ReadTests[i]
- if err := test.RunTest(headers); err != nil {
- return err
- }
- }
- for i := range s.WriteTests {
- test := &s.WriteTests[i]
- if err := test.RunTest(headers); err != nil {
- return err
- }
- }
- return nil
-}
-
-var SCOPES = []OpenAIScope{
- {
- ReadTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/models", Method: "GET", Valid: []int{200}, Invalid: []int{403}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},
- },
- Endpoints: []string{"/v1/models"},
- ReadPermission: ModelsRead,
- },
- {
- WriteTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/images/generations", Method: "POST", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},
- },
- Endpoints: []string{"/v1/audio", "/v1/chat/completions", "/v1/embeddings", "/v1/images", "/v1/moderations"},
- WritePermission: ModelCapabilitiesWrite,
- },
- {
- ReadTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/assistants", Method: "GET", Valid: []int{400}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},
- },
- WriteTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/assistants", Method: "POST", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},
- },
- Endpoints: []string{"/v1/assistants"},
- ReadPermission: AssistantsRead,
- WritePermission: AssistantsWrite,
- },
- {
- ReadTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/threads/1", Method: "GET", Valid: []int{400}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},
- },
- WriteTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/threads", Method: "POST", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},
- },
- Endpoints: []string{"/v1/threads"},
- ReadPermission: ThreadsRead,
- WritePermission: ThreadsWrite,
- },
- {
- ReadTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/fine_tuning/jobs", Method: "GET", Valid: []int{200}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},
- },
- WriteTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/fine_tuning/jobs", Method: "POST", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},
- },
- Endpoints: []string{"/v1/fine_tuning"},
- ReadPermission: FineTuningRead,
- WritePermission: FineTuningWrite,
- },
- {
- ReadTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/files", Method: "GET", Valid: []int{200}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},
- },
- WriteTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/files", Method: "POST", Payload: POST_PAYLOAD, Valid: []int{415}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},
- },
- Endpoints: []string{"/v1/files"},
- ReadPermission: FilesRead,
- WritePermission: FilesWrite,
- },
- {
- ReadTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/evals", Method: "GET", Valid: []int{200}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},
- },
- WriteTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/evals", Method: "POST", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},
- },
- Endpoints: []string{"/v1/evals"},
- ReadPermission: EvalsRead,
- WritePermission: EvalsWrite,
- },
- {
- ReadTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/responses/1", Method: "GET", Valid: []int{400}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},
- },
- WriteTests: []analyzers.HttpStatusTest{
- {URL: BASE_URL + "/v1/responses", Method: "POST", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},
- },
- Endpoints: []string{"/v1/responses"},
- ReadPermission: ResponsesRead,
- WritePermission: ResponsesWrite,
- },
-}
diff --git a/pkg/analyzer/analyzers/opsgenie/opsgenie.go b/pkg/analyzer/analyzers/opsgenie/opsgenie.go
deleted file mode 100644
index c18c5c990417..000000000000
--- a/pkg/analyzer/analyzers/opsgenie/opsgenie.go
+++ /dev/null
@@ -1,310 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go opsgenie
-
-package opsgenie
-
-import (
- "bytes"
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "os"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeOpsgenie }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("missing key in credInfo")
- }
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeOpsgenie,
- Metadata: nil,
- Bindings: make([]analyzers.Binding, len(info.Permissions)),
- UnboundedResources: make([]analyzers.Resource, len(info.Users)),
- }
-
- // Opsgenie has API integrations, so the key does not belong
- // to a particular user or account, it itself is a resource
- resource := analyzers.Resource{
- Name: "Opsgenie API Integration Key",
- FullyQualifiedName: "Opsgenie API Integration Key",
- Type: "API Key",
- Metadata: map[string]any{
- "expires": "never",
- },
- }
-
- for idx, permission := range info.Permissions {
- result.Bindings[idx] = analyzers.Binding{
- Resource: resource,
- Permission: analyzers.Permission{
- Value: permission,
- },
- }
- }
-
- // We can find list of users in the current account
- // if the API key has Configuration Access, so these can be
- // unbounded resources
- for idx, user := range info.Users {
- result.UnboundedResources[idx] = analyzers.Resource{
- Name: user.FullName,
- FullyQualifiedName: user.Username,
- Type: "user",
- Metadata: map[string]any{
- "username": user.Username,
- "role": user.Role.Name,
- },
- }
- }
-
- return &result
-}
-
-//go:embed scopes.json
-var scopesConfig []byte
-
-type User struct {
- FullName string `json:"fullName"`
- Username string `json:"username"`
- Role struct {
- Name string `json:"name"`
- } `json:"role"`
-}
-
-type UsersJSON struct {
- Users []User `json:"data"`
-}
-
-type HttpStatusTest struct {
- Endpoint string `json:"endpoint"`
- Method string `json:"method"`
- Payload interface{} `json:"payload"`
- ValidStatuses []int `json:"valid_status_code"`
- InvalidStatuses []int `json:"invalid_status_code"`
-}
-
-func StatusContains(status int, vals []int) bool {
- for _, v := range vals {
- if status == v {
- return true
- }
- }
- return false
-}
-
-func (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string) (bool, error) {
- // If body data, marshal to JSON
- var data io.Reader
- if h.Payload != nil {
- jsonData, err := json.Marshal(h.Payload)
- if err != nil {
- return false, err
- }
- data = bytes.NewBuffer(jsonData)
- }
-
- // Create new HTTP request
- var client *http.Client
-
- // Non-safe Opsgenie APIs are asynchronous and always return 202 if credential has the permission.
- // For Safe API Methods, use the restricted client
- if analyzers.IsMethodSafe(h.Method) {
- client = analyzers.NewAnalyzeClient(cfg)
- } else {
- client = analyzers.NewAnalyzeClientUnrestricted(cfg)
- }
-
- req, err := http.NewRequest(h.Method, h.Endpoint, data)
- if err != nil {
- return false, err
- }
-
- // Add custom headers if provided
- for key, value := range headers {
- req.Header.Set(key, value)
- }
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer resp.Body.Close()
-
- // Check response status code
- switch {
- case StatusContains(resp.StatusCode, h.ValidStatuses):
- return true, nil
- case StatusContains(resp.StatusCode, h.InvalidStatuses):
- return false, nil
- default:
- return false, errors.New("error checking response status code")
- }
-}
-
-type Scope struct {
- Name string `json:"name"`
- HttpTest HttpStatusTest `json:"test"`
-}
-
-func readInScopes() ([]Scope, error) {
- var scopes []Scope
- if err := json.Unmarshal(scopesConfig, &scopes); err != nil {
- return nil, err
- }
-
- return scopes, nil
-}
-
-func checkPermissions(cfg *config.Config, key string) ([]string, error) {
- scopes, err := readInScopes()
- if err != nil {
- return nil, fmt.Errorf("reading in scopes: %w", err)
- }
-
- permissions := make([]string, 0)
- for _, scope := range scopes {
- status, err := scope.HttpTest.RunTest(cfg, map[string]string{"Authorization": "GenieKey " + key})
- if err != nil {
- return nil, fmt.Errorf("running test: %w", err)
- }
- if status {
- permissions = append(permissions, scope.Name)
- }
- }
-
- return permissions, nil
-}
-
-func contains(s []string, e string) bool {
- for _, a := range s {
- if a == e {
- return true
- }
- }
- return false
-}
-
-func getUserList(cfg *config.Config, key string) ([]User, error) {
- // Create new HTTP request
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", "https://api.opsgenie.com/v2/users", nil)
- if err != nil {
- return nil, err
- }
-
- // Add custom headers if provided
- req.Header.Set("Authorization", "GenieKey "+key)
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- // Decode response body
- var userList UsersJSON
- err = json.NewDecoder(resp.Body).Decode(&userList)
- if err != nil {
- return nil, err
- }
-
- return userList.Users, nil
-}
-
-type SecretInfo struct {
- Users []User
- Permissions []string
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- color.Green("[!] Valid OpsGenie API key\n\n")
- printPermissions(info.Permissions)
- if len(info.Users) > 0 {
- printUsers(info.Users)
- }
- color.Yellow("\n[i] Expires: Never")
-
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- var info = &SecretInfo{}
-
- permissions, err := checkPermissions(cfg, key)
- if err != nil {
- return nil, err
- }
-
- if len(permissions) == 0 {
- return nil, fmt.Errorf("invalid OpsGenie API key")
- }
-
- info.Permissions = permissions
-
- if contains(permissions, "configuration_access") {
- users, err := getUserList(cfg, key)
- if err != nil {
- return nil, fmt.Errorf("getting user list: %w", err)
- }
- info.Users = users
- }
-
- return info, nil
-}
-
-func printPermissions(permissions []string) {
- color.Yellow("[i] Permissions:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission"})
- for _, permission := range permissions {
- t.AppendRow(table.Row{color.GreenString(permission)})
- }
- t.Render()
-}
-
-func printUsers(users []User) {
- color.Green("\n[i] Users:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Name", "Username", "Role"})
- for _, user := range users {
- t.AppendRow(table.Row{color.GreenString(user.FullName), color.GreenString(user.Username), color.GreenString(user.Role.Name)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/opsgenie/opsgenie_test.go b/pkg/analyzer/analyzers/opsgenie/opsgenie_test.go
deleted file mode 100644
index 490c030d7ebc..000000000000
--- a/pkg/analyzer/analyzers/opsgenie/opsgenie_test.go
+++ /dev/null
@@ -1,158 +0,0 @@
-package opsgenie
-
-import (
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- key := testSecrets.MustGetField("OPSGENIE")
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid Opsgenie API key",
- key: key,
- want: `{
- "AnalyzerType": 11,
- "Bindings": [
- {
- "Resource": {
- "Name": "Opsgenie API Integration Key",
- "FullyQualifiedName": "Opsgenie API Integration Key",
- "Type": "API Key",
- "Metadata": {
- "expires": "never"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "configuration_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Opsgenie API Integration Key",
- "FullyQualifiedName": "Opsgenie API Integration Key",
- "Type": "API Key",
- "Metadata": {
- "expires": "never"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Opsgenie API Integration Key",
- "FullyQualifiedName": "Opsgenie API Integration Key",
- "Type": "API Key",
- "Metadata": {
- "expires": "never"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Opsgenie API Integration Key",
- "FullyQualifiedName": "Opsgenie API Integration Key",
- "Type": "API Key",
- "Metadata": {
- "expires": "never"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "create_and_update",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": [
- {
- "Name": "John Scanner",
- "FullyQualifiedName": "secretscanner02@zohomail.com",
- "Type": "user",
- "Metadata": {
- "role": "Owner",
- "username": "secretscanner02@zohomail.com"
- },
- "Parent": null
- }
- ],
- "Metadata": null
- }`,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/opsgenie/permissions.go b/pkg/analyzer/analyzers/opsgenie/permissions.go
deleted file mode 100644
index 11824b2b42ce..000000000000
--- a/pkg/analyzer/analyzers/opsgenie/permissions.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package opsgenie
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- ConfigurationAccess Permission = iota
- Read Permission = iota
- Delete Permission = iota
- CreateAndUpdate Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- ConfigurationAccess: "configuration_access",
- Read: "read",
- Delete: "delete",
- CreateAndUpdate: "create_and_update",
- }
-
- StringToPermission = map[string]Permission{
- "configuration_access": ConfigurationAccess,
- "read": Read,
- "delete": Delete,
- "create_and_update": CreateAndUpdate,
- }
-
- PermissionIDs = map[Permission]int{
- ConfigurationAccess: 1,
- Read: 2,
- Delete: 3,
- CreateAndUpdate: 4,
- }
-
- IdToPermission = map[int]Permission{
- 1: ConfigurationAccess,
- 2: Read,
- 3: Delete,
- 4: CreateAndUpdate,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/opsgenie/permissions.yaml b/pkg/analyzer/analyzers/opsgenie/permissions.yaml
deleted file mode 100644
index cebe131a7d76..000000000000
--- a/pkg/analyzer/analyzers/opsgenie/permissions.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-permissions:
- - configuration_access
- - read
- - delete
- - create_and_update
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/opsgenie/scopes.json b/pkg/analyzer/analyzers/opsgenie/scopes.json
deleted file mode 100644
index d542fa76568d..000000000000
--- a/pkg/analyzer/analyzers/opsgenie/scopes.json
+++ /dev/null
@@ -1,38 +0,0 @@
-[
- {
- "name": "configuration_access",
- "test": {
- "endpoint": "https://api.opsgenie.com/v2/account",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "read",
- "test": {
- "endpoint": "https://api.opsgenie.com/v2/alerts",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "delete",
- "test": {
- "endpoint": "https://api.opsgenie.com/v2/alerts/`nowaythiscanexist",
- "method": "DELETE",
- "valid_status_code": [202],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "create_and_update",
- "test": {
- "endpoint": "https://api.opsgenie.com/v2/alerts/`nowaycanthisexist/message",
- "method": "PUT",
- "valid_status_code": [400],
- "invalid_status_code": [403]
- }
- }
-]
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/plaid/expected_output.json b/pkg/analyzer/analyzers/plaid/expected_output.json
deleted file mode 100644
index 14602a6f9342..000000000000
--- a/pkg/analyzer/analyzers/plaid/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":33,"Bindings":[{"Resource":{"Name":"Assets","FullyQualifiedName":"zkjAkPqvmyikkkyJMbBjCnEPEAR5kjio3KAM4/product/assets","Type":"product","Metadata":{"productDesc":"Request, retrieve and share detailed reports of financial assets and account history"},"Parent":null},"Permission":{"Value":"write","Parent":null}},{"Resource":{"Name":"Auth","FullyQualifiedName":"zkjAkPqvmyikkkyJMbBjCnEPEAR5kjio3KAM4/product/auth","Type":"product","Metadata":{"productDesc":"Retrieve account and routing numbers"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"Identity","FullyQualifiedName":"zkjAkPqvmyikkkyJMbBjCnEPEAR5kjio3KAM4/product/identity","Type":"product","Metadata":{"productDesc":"Access personal identity information like name, phone, address, and email"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"Investments","FullyQualifiedName":"zkjAkPqvmyikkkyJMbBjCnEPEAR5kjio3KAM4/product/investments","Type":"product","Metadata":{"productDesc":"Retrieve holdings, balances, and historical investment transactions"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"Liabilities","FullyQualifiedName":"zkjAkPqvmyikkkyJMbBjCnEPEAR5kjio3KAM4/product/liabilities","Type":"product","Metadata":{"productDesc":"Access detailed information about loans, credit cards, and other liabilities"},"Parent":null},"Permission":{"Value":"write","Parent":null}},{"Resource":{"Name":"Transactions","FullyQualifiedName":"zkjAkPqvmyikkkyJMbBjCnEPEAR5kjio3KAM4/product/transactions","Type":"product","Metadata":{"productDesc":"Retrieve, filter, and analyze categorized transaction history"},"Parent":null},"Permission":{"Value":"read","Parent":null}},{"Resource":{"Name":"Transfer","FullyQualifiedName":"zkjAkPqvmyikkkyJMbBjCnEPEAR5kjio3KAM4/product/transfer","Type":"product","Metadata":{"productDesc":"Initiate, manage, and track bank transfers"},"Parent":null},"Permission":{"Value":"write","Parent":null}}],"UnboundedResources":[{"Name":"Plaid Checking","FullyQualifiedName":"K1xy1qQJn8u555qNpjrbFxba95ydRxCRpw1nM","Type":"account","Metadata":{"officialName":"Plaid Gold Standard 0% Interest Checking"},"Parent":null},{"Name":"Plaid Saving","FullyQualifiedName":"rpBKpzVvJ8S333ZPGE8bcg8PvJbG4gC7JK1on","Type":"account","Metadata":{"officialName":"Plaid Silver Standard 0.1% Interest Saving"},"Parent":null},{"Name":"Plaid CD","FullyQualifiedName":"zkjAkPqvmyikkkyJMbBjCnEP1lepvnClNPQq1","Type":"account","Metadata":{"officialName":"Plaid Bronze Standard 0.2% Interest CD"},"Parent":null},{"Name":"Plaid Credit Card","FullyQualifiedName":"B4Vv4GxJReIEEEj6Lz9viM6XpLBRzMT4MegZn","Type":"account","Metadata":{"officialName":"Plaid Diamond 12.5% APR Interest Credit Card"},"Parent":null},{"Name":"Plaid Money Market","FullyQualifiedName":"3y8LyjgMKltRRR1aNd5zHEGl8Q3LWEHZloEPn","Type":"account","Metadata":{"officialName":"Plaid Platinum Standard 1.85% Interest Money Market"},"Parent":null},{"Name":"Plaid IRA","FullyQualifiedName":"ed9ydAVLvNUjjjPMG5N6CXN6yWry9jtr96mEk","Type":"account","Metadata":{"officialName":""},"Parent":null},{"Name":"Plaid 401k","FullyQualifiedName":"QEj7ExolJbi555xEeqBbFjE4qJ3qQbtwmyDzD","Type":"account","Metadata":{"officialName":""},"Parent":null},{"Name":"Plaid Student Loan","FullyQualifiedName":"ZvopvQVaqlSKKKQak4lrCgl14Wd4yXfeqQGz1","Type":"account","Metadata":{"officialName":""},"Parent":null},{"Name":"Plaid Mortgage","FullyQualifiedName":"MpaRprMJ7WS555r49WVdFabjPmyPeDUL6n1zX","Type":"account","Metadata":{"officialName":""},"Parent":null},{"Name":"Plaid HSA","FullyQualifiedName":"1boLbNpQlXSqqqoM7e53trlEbaAb91FpjWr3X","Type":"account","Metadata":{"officialName":"Plaid Cares Health Savings Account"},"Parent":null},{"Name":"Plaid Cash Management","FullyQualifiedName":"LLdQLKZJVEH555KNbnxaFd3RwEawW9ukjDEoj","Type":"account","Metadata":{"officialName":"Plaid Growth Cash Management"},"Parent":null},{"Name":"Plaid Business Credit Card","FullyQualifiedName":"p1rl1KVv5ouzzzkMEVo1s5vBPXyPo9UpoRzkM","Type":"account","Metadata":{"officialName":"Plaid Platinum Small Business Credit Card"},"Parent":null}],"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/plaid/models.go b/pkg/analyzer/analyzers/plaid/models.go
deleted file mode 100644
index 98439854f6cb..000000000000
--- a/pkg/analyzer/analyzers/plaid/models.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package plaid
-
-type account struct {
- AccountID string `json:"account_id"`
- Name string `json:"name"`
- OfficialName string `json:"official_name"`
- Subtype string `json:"subtype"`
- Type string `json:"type"`
-}
-
-type item struct {
- Products []string `json:"products"`
- ItemID string `json:"item_id"`
-}
-
-type accountsResponse struct {
- Accounts []account `json:"accounts"`
- Item item `json:"item"`
-}
-
-type secretInfo struct {
- Item item
- Accounts []account
- Environment string
-}
diff --git a/pkg/analyzer/analyzers/plaid/permissions.go b/pkg/analyzer/analyzers/plaid/permissions.go
deleted file mode 100644
index 42add62a1fa4..000000000000
--- a/pkg/analyzer/analyzers/plaid/permissions.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package plaid
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Read Permission = iota
- Write Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Read: "read",
- Write: "write",
- }
-
- StringToPermission = map[string]Permission{
- "read": Read,
- "write": Write,
- }
-
- PermissionIDs = map[Permission]int{
- Read: 1,
- Write: 2,
- }
-
- IdToPermission = map[int]Permission{
- 1: Read,
- 2: Write,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/plaid/permissions.yaml b/pkg/analyzer/analyzers/plaid/permissions.yaml
deleted file mode 100644
index 9bc58ca5bcc4..000000000000
--- a/pkg/analyzer/analyzers/plaid/permissions.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-permissions:
- - read
- - write
diff --git a/pkg/analyzer/analyzers/plaid/plaid.go b/pkg/analyzer/analyzers/plaid/plaid.go
deleted file mode 100644
index d35d2580a551..000000000000
--- a/pkg/analyzer/analyzers/plaid/plaid.go
+++ /dev/null
@@ -1,227 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go plaid
-package plaid
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "os"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (a Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypePlaid
-}
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- secret, exist := credInfo["secret"]
- if !exist {
- return nil, errors.New("secret not found in credentials info")
- }
- clientID, exist := credInfo["id"]
- if !exist {
- return nil, errors.New("id not found in credentials info")
- }
- accessToken, exist := credInfo["token"]
- if !exist {
- return nil, errors.New("token not found in credentials info")
- }
-
- info, err := AnalyzePermissions(a.Cfg, secret, clientID, accessToken)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, secret string, clientID string, accessToken string) {
- info, err := AnalyzePermissions(cfg, secret, clientID, accessToken)
- if err != nil {
- color.Red("[x] Invalid Plaid API key\n")
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- if info == nil {
- color.Red("[x] Error : %s", "No information found")
- return
- }
-
- color.Green("[i] Valid Plaid API Credentials\n")
- color.Yellow("\n[i] Environment: %s", info.Environment)
- if info.Environment == "sandbox" {
- color.Cyan("Credentials are for Sandbox environment. All resources found are simulated and not real data.\n")
- }
- printAccountsAndProducts(info)
-}
-
-func AnalyzePermissions(cfg *config.Config, secret string, clientId string, accessToken string) (*secretInfo, error) {
- environment := "sandbox"
- if strings.Contains(accessToken, "production") {
- environment = "production"
- }
-
- // Plaid API uses POST requests for all requests, so we need to use an unrestricted client
- client := analyzers.NewAnalyzeClientUnrestricted(cfg)
- var secretInfo = &secretInfo{}
- secretInfo.Environment = environment
- resp, err := getPlaidAccounts(client, clientId, secret, accessToken, environment)
- if err != nil {
- return nil, err
- }
- secretInfo.Item = resp.Item
- secretInfo.Accounts = resp.Accounts
- return secretInfo, nil
-}
-
-func getPlaidAccounts(client *http.Client, clientID string, secret string, accessToken string, environment string) (*accountsResponse, error) {
- body := map[string]interface{}{
- "client_id": clientID,
- "secret": secret,
- "access_token": accessToken,
- }
- url := "https://" + environment + ".plaid.com/accounts/get"
- jsonBody, _ := json.Marshal(body)
- req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonBody))
- if err != nil {
- return nil, err
- }
- req.Header.Set("Content-Type", "application/json")
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("received non-OK HTTP status: %d", resp.StatusCode)
- }
-
- var accounts accountsResponse
- if err := json.NewDecoder(resp.Body).Decode(&accounts); err != nil {
- return nil, err
- }
-
- return &accounts, nil
-}
-
-func secretInfoToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- itemID := info.Item.ItemID
- userProducts := info.Item.Products
- userAccounts := info.Accounts
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypePlaid,
- Metadata: nil,
- Bindings: make([]analyzers.Binding, len(userProducts)),
- UnboundedResources: make([]analyzers.Resource, len(userAccounts)),
- }
-
- for idx, productName := range userProducts {
- product, ok := GetProductByName(productName)
- if !ok {
- continue
- }
- result.Bindings[idx] = analyzers.Binding{
- Resource: analyzers.Resource{
- Name: product.DisplayName,
- FullyQualifiedName: itemID + "/product/" + product.Name,
- Type: "product",
- Metadata: map[string]any{
- "productDesc": product.Description,
- },
- },
- Permission: analyzers.Permission{
- Value: PermissionStrings[product.PermissionLevel],
- },
- }
- }
-
- for idx, account := range info.Accounts {
- result.UnboundedResources[idx] = analyzers.Resource{
- Name: account.Name,
- FullyQualifiedName: account.AccountID,
- Type: "account",
- Metadata: map[string]any{
- "officialName": account.OfficialName,
- },
- }
- }
-
- return &result
-}
-
-func printAccountsAndProducts(info *secretInfo) {
- userProducts := info.Item.Products
- userAccounts := info.Accounts
-
- color.Yellow("\n[i] Item ID: %s", info.Item.ItemID)
-
- color.Yellow("\n[i] Accounts Info:")
- t1 := table.NewWriter()
- t1.SetOutputMirror(os.Stdout)
- t1.AppendHeader(table.Row{"ID", "Name", "Official Name", "Type", "Subtype"})
- for _, account := range userAccounts {
- t1.AppendRow(table.Row{
- color.GreenString(account.AccountID),
- color.GreenString(account.Name),
- color.GreenString(account.OfficialName),
- color.GreenString(account.Type),
- color.GreenString(account.Subtype),
- })
- t1.AppendSeparator()
- }
- t1.SetOutputMirror(os.Stdout)
- t1.Render()
-
- color.Yellow("\n[i] Products:")
- t2 := table.NewWriter()
- t2.AppendHeader(table.Row{"Product Name", "Access Level", "Capabilities"})
-
- for _, product := range plaidProducts {
- productCell := color.GreenString(product.DisplayName)
- productDescCell := color.GreenString(product.Description)
- productPermissionCell := color.GreenString("Denied")
-
- for _, productName := range userProducts {
- if productName == product.Name {
- permissionLevel := PermissionStrings[product.PermissionLevel]
- productPermissionCell = "Granted" // If permission level is not defined, default to "Granted"
- if len(permissionLevel) > 0 {
- // Capitalize the perssion level string
- capitalizedLevel := strings.ToUpper(string(permissionLevel[0])) + strings.ToLower(permissionLevel[1:])
- productPermissionCell = color.GreenString(capitalizedLevel)
- }
- break
- }
- }
-
- t2.AppendRow(table.Row{productCell, productPermissionCell, productDescCell})
- t2.AppendSeparator()
- }
-
- t2.SetOutputMirror(os.Stdout)
- t2.Render()
- fmt.Printf("%s: https://plaid.com/docs/api/\n\n", color.GreenString("Ref"))
-}
diff --git a/pkg/analyzer/analyzers/plaid/plaid_test.go b/pkg/analyzer/analyzers/plaid/plaid_test.go
deleted file mode 100644
index a6de49d29bd2..000000000000
--- a/pkg/analyzer/analyzers/plaid/plaid_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package plaid
-
-import (
- _ "embed"
- "encoding/json"
- "fmt"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- secret := testSecrets.MustGetField("PLAIDKEY_SECRET")
- clientID := testSecrets.MustGetField("PLAIDKEY_CLIENTID")
- accessToken := testSecrets.MustGetField("PLAIDKEY_ACCESS_TOKEN")
-
- tests := []struct {
- name string
- clientID string
- secret string
- accessToken string
- want string
- wantErr bool
- }{
- {
- name: "valid plaid credentials",
- clientID: clientID,
- secret: secret,
- accessToken: accessToken,
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{
- "secret": tt.secret,
- "id": tt.clientID,
- "token": tt.accessToken,
- })
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- fmt.Println(string(gotJSON))
-
- // compare the JSON strings
- if string(gotJSON) != string(tt.want) {
- // pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(tt.want, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/plaid/products.go b/pkg/analyzer/analyzers/plaid/products.go
deleted file mode 100644
index b55758f5697c..000000000000
--- a/pkg/analyzer/analyzers/plaid/products.go
+++ /dev/null
@@ -1,256 +0,0 @@
-package plaid
-
-type plaidProduct struct {
- Name string
- DisplayName string
- Description string
- PermissionLevel Permission
-}
-
-type Product int
-
-const (
- Assets Product = iota
- Auth
- Balance
- BalancePlus
- Beacon
- CraBaseReport
- CraIncomeInsights
- CraPartnerInsights
- CraNetworkInsights
- CraCashflowInsights
- CreditDetails
- Employment
- Identity
- IdentityMatch
- IdentityVerification
- Income
- IncomeVerification
- Investments
- InvestmentsAuth
- Layer
- Liabilities
- PayByBank
- PaymentInitiation
- ProcessorPayments
- ProcessorIdentity
- Profile
- RecurringTransactions
- Signal
- StandingOrders
- Statements
- Transactions
- TransactionsRefresh
- Transfer
-)
-
-var plaidProducts = map[Product]plaidProduct{
- Assets: {
- Name: "assets",
- DisplayName: "Assets",
- Description: "Request, retrieve and share detailed reports of financial assets and account history",
- PermissionLevel: Write,
- },
- Auth: {
- Name: "auth",
- DisplayName: "Auth",
- Description: "Retrieve account and routing numbers",
- PermissionLevel: Read,
- },
- Balance: {
- Name: "balance",
- DisplayName: "Balance",
- Description: "Check current and available account balance in real time",
- PermissionLevel: Read,
- },
- BalancePlus: {
- Name: "balance_plus",
- DisplayName: "Balance Plus",
- Description: "Estimate projected balances and financial runway",
- PermissionLevel: Read,
- },
- Beacon: {
- Name: "beacon",
- DisplayName: "Beacon",
- Description: "Generate risk insights and fraud signals based on user account behavior",
- PermissionLevel: Write,
- },
- CraBaseReport: {
- Name: "cra_base_report",
- DisplayName: "CRA Base Report",
- Description: "Generate a standardized financial report",
- PermissionLevel: Write,
- },
- CraIncomeInsights: {
- Name: "cra_income_insights",
- DisplayName: "CRA Income Insights",
- Description: "Analyze income trends and consistency",
- PermissionLevel: Write,
- },
- CraPartnerInsights: {
- Name: "cra_partner_insights",
- DisplayName: "CRA Partner Insights",
- Description: "Access custom insights",
- PermissionLevel: Write,
- },
- CraNetworkInsights: {
- Name: "cra_network_insights",
- DisplayName: "CRA Network Insights",
- Description: "View analytics and performance benchmarks",
- PermissionLevel: Write,
- },
- CraCashflowInsights: {
- Name: "cra_cashflow_insights",
- DisplayName: "CRA Cashflow Insights",
- Description: "Evaluate cash flow behavior including recurring income and expenses",
- PermissionLevel: Write,
- },
- CreditDetails: {
- Name: "credit_details",
- DisplayName: "Credit Details",
- Description: "Access credit account usage, limits, and repayment history",
- PermissionLevel: Read,
- },
- Employment: {
- Name: "employment",
- DisplayName: "Employment",
- Description: "Retrieve current employment status and employer details",
- PermissionLevel: Read,
- },
- Identity: {
- Name: "identity",
- DisplayName: "Identity",
- Description: "Access personal identity information like name, phone, address, and email",
- PermissionLevel: Read,
- },
- IdentityMatch: {
- Name: "identity_match",
- DisplayName: "Identity Match",
- Description: "Match user-provided identity details against institution records",
- PermissionLevel: Read,
- },
- IdentityVerification: {
- Name: "identity_verification",
- DisplayName: "Identity Verification",
- Description: "Verify user identity through government documents and identity data sources",
- PermissionLevel: Write,
- },
- Income: {
- Name: "income",
- DisplayName: "Income",
- Description: "Analyze income patterns based on transaction history",
- PermissionLevel: Write,
- },
- IncomeVerification: {
- Name: "income_verification",
- DisplayName: "Income Verification",
- Description: "Verify income through paystubs, payroll data, or bank information",
- PermissionLevel: Write,
- },
- Investments: {
- Name: "investments",
- DisplayName: "Investments",
- Description: "Retrieve holdings, balances, and historical investment transactions",
- PermissionLevel: Read,
- },
- InvestmentsAuth: {
- Name: "investments_auth",
- DisplayName: "Investments Auth",
- Description: "Retrieve account and routing numbers for investment accounts",
- PermissionLevel: Read,
- },
- Layer: {
- Name: "layer",
- DisplayName: "Layer",
- Description: "Use a simplified onboarding experience for linking financial accounts",
- PermissionLevel: Read,
- },
- Liabilities: {
- Name: "liabilities",
- DisplayName: "Liabilities",
- Description: "Access detailed information about loans, credit cards, and other liabilities",
- PermissionLevel: Write,
- },
- PayByBank: {
- Name: "pay_by_bank",
- DisplayName: "Pay By Bank",
- Description: "Initiate payments directly from the user's bank account",
- PermissionLevel: Write,
- },
- PaymentInitiation: {
- Name: "payment_initiation",
- DisplayName: "Payment Initiation",
- Description: "Create and manage payment requests and track their status",
- PermissionLevel: Write,
- },
- ProcessorPayments: {
- Name: "processor_payments",
- DisplayName: "Processor Payments",
- Description: "Send payment details securely to third-party processors",
- PermissionLevel: Write,
- },
- ProcessorIdentity: {
- Name: "processor_identity",
- DisplayName: "Processor Identity",
- Description: "Share identity data with payment processors for verification",
- PermissionLevel: Read,
- },
- Profile: {
- Name: "profile",
- DisplayName: "Profile",
- Description: "Access user profile data",
- PermissionLevel: Read,
- },
- RecurringTransactions: {
- Name: "recurring_transactions",
- DisplayName: "Recurring Transactions",
- Description: "Identify and analyze recurring payments and subscriptions",
- PermissionLevel: Write,
- },
- Signal: {
- Name: "signal",
- DisplayName: "Signal",
- Description: "Assess the likelihood of ACH returns",
- PermissionLevel: Read,
- },
- StandingOrders: {
- Name: "standing_orders",
- DisplayName: "Standing Orders",
- Description: "View and manage recurring scheduled bank transfers",
- PermissionLevel: Write,
- },
- Statements: {
- Name: "statements",
- DisplayName: "Statements",
- Description: "List and download historical bank statements in PDF format",
- PermissionLevel: Read,
- },
- Transactions: {
- Name: "transactions",
- DisplayName: "Transactions",
- Description: "Retrieve, filter, and analyze categorized transaction history",
- PermissionLevel: Read,
- },
- TransactionsRefresh: {
- Name: "transactions_refresh",
- DisplayName: "Transactions Refresh",
- Description: "Trigger a manual refresh to retrieve the latest transactions",
- PermissionLevel: Read,
- },
- Transfer: {
- Name: "transfer",
- DisplayName: "Transfer",
- Description: "Initiate, manage, and track bank transfers",
- PermissionLevel: Write,
- },
-}
-
-func GetProductByName(name string) (plaidProduct, bool) {
- for _, product := range plaidProducts {
- if product.Name == name {
- return product, true
- }
- }
- return plaidProduct{}, false
-}
diff --git a/pkg/analyzer/analyzers/planetscale/expected_output.json b/pkg/analyzer/analyzers/planetscale/expected_output.json
deleted file mode 100644
index 4a63175c7c18..000000000000
--- a/pkg/analyzer/analyzers/planetscale/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":27,"Bindings":[{"Resource":{"Name":"detector-db","FullyQualifiedName":"planetscale.com/database/9p2lzxigxod0","Type":"Database","Metadata":null,"Parent":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null}},"Permission":{"Value":"connect_branch","Parent":null}},{"Resource":{"Name":"detector-db","FullyQualifiedName":"planetscale.com/database/9p2lzxigxod0","Type":"Database","Metadata":null,"Parent":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null}},"Permission":{"Value":"connect_production_branch","Parent":null}},{"Resource":{"Name":"detector-db","FullyQualifiedName":"planetscale.com/database/9p2lzxigxod0","Type":"Database","Metadata":null,"Parent":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null}},"Permission":{"Value":"create_branch","Parent":null}},{"Resource":{"Name":"detector-db","FullyQualifiedName":"planetscale.com/database/9p2lzxigxod0","Type":"Database","Metadata":null,"Parent":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null}},"Permission":{"Value":"create_deploy_request","Parent":null}},{"Resource":{"Name":"detector-db","FullyQualifiedName":"planetscale.com/database/9p2lzxigxod0","Type":"Database","Metadata":null,"Parent":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null}},"Permission":{"Value":"read_backups","Parent":null}},{"Resource":{"Name":"detector-db","FullyQualifiedName":"planetscale.com/database/9p2lzxigxod0","Type":"Database","Metadata":null,"Parent":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null}},"Permission":{"Value":"read_branch","Parent":null}},{"Resource":{"Name":"detector-db","FullyQualifiedName":"planetscale.com/database/9p2lzxigxod0","Type":"Database","Metadata":null,"Parent":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null}},"Permission":{"Value":"read_database","Parent":null}},{"Resource":{"Name":"detector-db","FullyQualifiedName":"planetscale.com/database/9p2lzxigxod0","Type":"Database","Metadata":null,"Parent":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null}},"Permission":{"Value":"read_deploy_request","Parent":null}},{"Resource":{"Name":"detector-db","FullyQualifiedName":"planetscale.com/database/9p2lzxigxod0","Type":"Database","Metadata":null,"Parent":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null}},"Permission":{"Value":"restore_backup","Parent":null}},{"Resource":{"Name":"detector-db","FullyQualifiedName":"planetscale.com/database/9p2lzxigxod0","Type":"Database","Metadata":null,"Parent":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null}},"Permission":{"Value":"restore_production_branch_backup","Parent":null}},{"Resource":{"Name":"detector-db","FullyQualifiedName":"planetscale.com/database/9p2lzxigxod0","Type":"Database","Metadata":null,"Parent":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null}},"Permission":{"Value":"write_database","Parent":null}},{"Resource":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null},"Permission":{"Value":"create_databases","Parent":null}},{"Resource":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null},"Permission":{"Value":"read_audit_logs","Parent":null}},{"Resource":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null},"Permission":{"Value":"read_databases","Parent":null}},{"Resource":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null},"Permission":{"Value":"read_invoices","Parent":null}},{"Resource":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null},"Permission":{"Value":"read_oauth_applications","Parent":null}},{"Resource":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null},"Permission":{"Value":"read_oauth_tokens","Parent":null}},{"Resource":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null},"Permission":{"Value":"read_organization","Parent":null}},{"Resource":{"Name":"detectors","FullyQualifiedName":"planetscale.com/organization/hn31ztkm9u15","Type":"Organization","Metadata":null,"Parent":null},"Permission":{"Value":"write_oauth_tokens","Parent":null}}],"UnboundedResources":null,"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/planetscale/permissions.go b/pkg/analyzer/analyzers/planetscale/permissions.go
deleted file mode 100644
index f071af287748..000000000000
--- a/pkg/analyzer/analyzers/planetscale/permissions.go
+++ /dev/null
@@ -1,216 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package planetscale
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- ReadOrganization Permission = iota
- ReadInvoices Permission = iota
- ReadDatabases Permission = iota
- ReadAuditLogs Permission = iota
- CreateDatabases Permission = iota
- DeleteDatabases Permission = iota
- ReadOauthApplications Permission = iota
- WriteOauthTokens Permission = iota
- ReadOauthTokens Permission = iota
- DeleteOauthTokens Permission = iota
- ReadDatabase Permission = iota
- WriteDatabase Permission = iota
- DeleteDatabase Permission = iota
- ReadBranch Permission = iota
- CreateBranch Permission = iota
- DeleteBranch Permission = iota
- DeleteBranchPassword Permission = iota
- DeleteProductionBranch Permission = iota
- DeleteProductionBranchPassword Permission = iota
- ReadDeployRequest Permission = iota
- CreateDeployRequest Permission = iota
- ApproveDeployRequest Permission = iota
- ConnectBranch Permission = iota
- ConnectProductionBranch Permission = iota
- ReadComment Permission = iota
- CreateComment Permission = iota
- RestoreBackup Permission = iota
- WriteBackups Permission = iota
- ReadBackups Permission = iota
- DeleteBackups Permission = iota
- RestoreProductionBranchBackup Permission = iota
- DeleteProductionBranchBackups Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- ReadOrganization: "read_organization",
- ReadInvoices: "read_invoices",
- ReadDatabases: "read_databases",
- ReadAuditLogs: "read_audit_logs",
- CreateDatabases: "create_databases",
- DeleteDatabases: "delete_databases",
- ReadOauthApplications: "read_oauth_applications",
- WriteOauthTokens: "write_oauth_tokens",
- ReadOauthTokens: "read_oauth_tokens",
- DeleteOauthTokens: "delete_oauth_tokens",
- ReadDatabase: "read_database",
- WriteDatabase: "write_database",
- DeleteDatabase: "delete_database",
- ReadBranch: "read_branch",
- CreateBranch: "create_branch",
- DeleteBranch: "delete_branch",
- DeleteBranchPassword: "delete_branch_password",
- DeleteProductionBranch: "delete_production_branch",
- DeleteProductionBranchPassword: "delete_production_branch_password",
- ReadDeployRequest: "read_deploy_request",
- CreateDeployRequest: "create_deploy_request",
- ApproveDeployRequest: "approve_deploy_request",
- ConnectBranch: "connect_branch",
- ConnectProductionBranch: "connect_production_branch",
- ReadComment: "read_comment",
- CreateComment: "create_comment",
- RestoreBackup: "restore_backup",
- WriteBackups: "write_backups",
- ReadBackups: "read_backups",
- DeleteBackups: "delete_backups",
- RestoreProductionBranchBackup: "restore_production_branch_backup",
- DeleteProductionBranchBackups: "delete_production_branch_backups",
- }
-
- StringToPermission = map[string]Permission{
- "read_organization": ReadOrganization,
- "read_invoices": ReadInvoices,
- "read_databases": ReadDatabases,
- "read_audit_logs": ReadAuditLogs,
- "create_databases": CreateDatabases,
- "delete_databases": DeleteDatabases,
- "read_oauth_applications": ReadOauthApplications,
- "write_oauth_tokens": WriteOauthTokens,
- "read_oauth_tokens": ReadOauthTokens,
- "delete_oauth_tokens": DeleteOauthTokens,
- "read_database": ReadDatabase,
- "write_database": WriteDatabase,
- "delete_database": DeleteDatabase,
- "read_branch": ReadBranch,
- "create_branch": CreateBranch,
- "delete_branch": DeleteBranch,
- "delete_branch_password": DeleteBranchPassword,
- "delete_production_branch": DeleteProductionBranch,
- "delete_production_branch_password": DeleteProductionBranchPassword,
- "read_deploy_request": ReadDeployRequest,
- "create_deploy_request": CreateDeployRequest,
- "approve_deploy_request": ApproveDeployRequest,
- "connect_branch": ConnectBranch,
- "connect_production_branch": ConnectProductionBranch,
- "read_comment": ReadComment,
- "create_comment": CreateComment,
- "restore_backup": RestoreBackup,
- "write_backups": WriteBackups,
- "read_backups": ReadBackups,
- "delete_backups": DeleteBackups,
- "restore_production_branch_backup": RestoreProductionBranchBackup,
- "delete_production_branch_backups": DeleteProductionBranchBackups,
- }
-
- PermissionIDs = map[Permission]int{
- ReadOrganization: 1,
- ReadInvoices: 2,
- ReadDatabases: 3,
- ReadAuditLogs: 4,
- CreateDatabases: 5,
- DeleteDatabases: 6,
- ReadOauthApplications: 7,
- WriteOauthTokens: 8,
- ReadOauthTokens: 9,
- DeleteOauthTokens: 10,
- ReadDatabase: 11,
- WriteDatabase: 12,
- DeleteDatabase: 13,
- ReadBranch: 14,
- CreateBranch: 15,
- DeleteBranch: 16,
- DeleteBranchPassword: 17,
- DeleteProductionBranch: 18,
- DeleteProductionBranchPassword: 19,
- ReadDeployRequest: 20,
- CreateDeployRequest: 21,
- ApproveDeployRequest: 22,
- ConnectBranch: 23,
- ConnectProductionBranch: 24,
- ReadComment: 25,
- CreateComment: 26,
- RestoreBackup: 27,
- WriteBackups: 28,
- ReadBackups: 29,
- DeleteBackups: 30,
- RestoreProductionBranchBackup: 31,
- DeleteProductionBranchBackups: 32,
- }
-
- IdToPermission = map[int]Permission{
- 1: ReadOrganization,
- 2: ReadInvoices,
- 3: ReadDatabases,
- 4: ReadAuditLogs,
- 5: CreateDatabases,
- 6: DeleteDatabases,
- 7: ReadOauthApplications,
- 8: WriteOauthTokens,
- 9: ReadOauthTokens,
- 10: DeleteOauthTokens,
- 11: ReadDatabase,
- 12: WriteDatabase,
- 13: DeleteDatabase,
- 14: ReadBranch,
- 15: CreateBranch,
- 16: DeleteBranch,
- 17: DeleteBranchPassword,
- 18: DeleteProductionBranch,
- 19: DeleteProductionBranchPassword,
- 20: ReadDeployRequest,
- 21: CreateDeployRequest,
- 22: ApproveDeployRequest,
- 23: ConnectBranch,
- 24: ConnectProductionBranch,
- 25: ReadComment,
- 26: CreateComment,
- 27: RestoreBackup,
- 28: WriteBackups,
- 29: ReadBackups,
- 30: DeleteBackups,
- 31: RestoreProductionBranchBackup,
- 32: DeleteProductionBranchBackups,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/planetscale/permissions.yaml b/pkg/analyzer/analyzers/planetscale/permissions.yaml
deleted file mode 100644
index 45a0b1fc27c9..000000000000
--- a/pkg/analyzer/analyzers/planetscale/permissions.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-permissions:
- - read_organization
- - read_invoices
- - read_databases
- - read_audit_logs
- - create_databases
- - delete_databases
- - read_oauth_applications
- - write_oauth_tokens
- - read_oauth_tokens
- - delete_oauth_tokens
- - read_database
- - write_database
- - delete_database
- - read_branch
- - create_branch
- - delete_branch
- - delete_branch_password
- - delete_production_branch
- - delete_production_branch_password
- - read_deploy_request
- - create_deploy_request
- - approve_deploy_request
- - connect_branch
- - connect_production_branch
- - read_comment
- - create_comment
- - restore_backup
- - write_backups
- - read_backups
- - delete_backups
- - restore_production_branch_backup
- - delete_production_branch_backups
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/planetscale/planetscale.go b/pkg/analyzer/analyzers/planetscale/planetscale.go
deleted file mode 100644
index 412fc753a215..000000000000
--- a/pkg/analyzer/analyzers/planetscale/planetscale.go
+++ /dev/null
@@ -1,598 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go planetscale
-
-package planetscale
-
-import (
- "bytes"
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "os"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypePlanetScale }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- id, ok := credInfo["id"]
- if !ok {
- return nil, errors.New("missing id in credInfo")
- }
- key, ok := credInfo["token"]
- if !ok {
- return nil, errors.New("missing key in credInfo")
- }
- info, err := AnalyzePermissions(a.Cfg, id, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypePlanetScale,
- Metadata: nil,
- Bindings: make([]analyzers.Binding, 0),
- }
-
- resource := analyzers.Resource{
- Name: info.Organization.Name,
- FullyQualifiedName: "planetscale.com/organization/" + info.Organization.Id,
- Type: "Organization",
- }
-
- for _, permission := range info.OrgPermissions {
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: resource,
- Permission: analyzers.Permission{
- Value: permission,
- },
- })
- }
-
- for db, permissions := range info.DBPermissions {
- dbResource := analyzers.Resource{
- Name: db.Name,
- FullyQualifiedName: "planetscale.com/database/" + db.Id,
- Type: "Database",
- Parent: &resource,
- }
- for _, permission := range permissions {
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: dbResource,
- Permission: analyzers.Permission{
- Value: permission,
- },
- })
- }
- }
- return &result
-}
-
-//go:embed scopes.json
-var scopesConfig []byte
-
-type HttpStatusTest struct {
- Endpoint string `json:"endpoint"`
- Method string `json:"method"`
- Payload interface{} `json:"payload"`
- ValidStatuses []int `json:"valid_status_code"`
- InvalidStatuses []int `json:"invalid_status_code"`
-}
-
-func StatusContains(status int, vals []int) bool {
- for _, v := range vals {
- if status == v {
- return true
- }
- }
- return false
-}
-
-func (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string, args ...any) (bool, error) {
- // If body data, marshal to JSON
- var data io.Reader
- if h.Payload != nil {
- jsonData, err := json.Marshal(h.Payload)
- if err != nil {
- return false, err
- }
- data = bytes.NewBuffer(jsonData)
- }
-
- // Create new HTTP request
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest(h.Method, fmt.Sprintf(h.Endpoint, args...), data)
- if err != nil {
- return false, err
- }
-
- // Add custom headers if provided
- for key, value := range headers {
- req.Header.Set(key, value)
- }
- req.Header.Add("Content-Type", "application/json")
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer resp.Body.Close()
-
- // Check response status code
- switch {
- case StatusContains(resp.StatusCode, h.ValidStatuses):
- return true, nil
- case StatusContains(resp.StatusCode, h.InvalidStatuses):
- return false, nil
- default:
- return false, errors.New("error checking response status code")
- }
-}
-
-type Scopes struct {
- OrganizationScopes []Scope `json:"organization_scopes"`
- OAuthApplicationScopes []Scope `json:"oauth_application_scopes"`
- DatabaseScopes []Scope `json:"database_scopes"`
- DeployRequestScopes []Scope `json:"deploy_request_scopes"`
- BranchScopes []BranchScope `json:"branch_scopes"`
- BackupScopes []BranchScope `json:"backup_scopes"`
-}
-
-type Scope struct {
- Name string `json:"name"`
- HttpTest HttpStatusTest `json:"test"`
-}
-
-type BranchScope struct {
- Scope
- Production bool `json:"production"`
-}
-
-func readInScopes() (*Scopes, error) {
- var scopes Scopes
- if err := json.Unmarshal(scopesConfig, &scopes); err != nil {
- return nil, err
- }
-
- return &scopes, nil
-}
-
-func checkPermissions(cfg *config.Config, scopes []Scope, id, key string, args ...any) ([]string, error) {
-
- permissions := make([]string, 0)
- for _, scope := range scopes {
- status, err := scope.HttpTest.RunTest(cfg, map[string]string{"Authorization": fmt.Sprintf("%s:%s", id, key)}, args...)
- if err != nil {
- return nil, fmt.Errorf("running test: %w", err)
- }
- if status {
- permissions = append(permissions, scope.Name)
- }
- }
-
- return permissions, nil
-}
-
-func checkBranchPermissions(cfg *config.Config, scopes []BranchScope, id, key, organization, db, branch string, production bool) ([]string, error) {
- permissions := make([]string, 0)
- for _, scope := range scopes {
- // check if scope is for production or non production branch
- if production != scope.Production {
- continue
- }
- status, err := scope.HttpTest.RunTest(cfg, map[string]string{"Authorization": fmt.Sprintf("%s:%s", id, key)}, organization, db, branch)
- if err != nil {
- return nil, fmt.Errorf("running test: %w", err)
- }
- if status {
- permissions = append(permissions, scope.Name)
- }
- }
-
- return permissions, nil
-}
-
-func checkBackupPermissions(cfg *config.Config, scopes []BranchScope, id, key, organization, db, backupId string, production bool) ([]string, error) {
- permissions := make([]string, 0)
- for _, scope := range scopes {
- // check if scope is for production or non production branch
- if production != scope.Production {
- continue
- }
- scope.HttpTest.Payload = map[string]string{"backup_id": backupId}
- status, err := scope.HttpTest.RunTest(cfg, map[string]string{"Authorization": fmt.Sprintf("%s:%s", id, key)}, organization, db)
- if err != nil {
- return nil, fmt.Errorf("running test: %w", err)
- }
- if status {
- permissions = append(permissions, scope.Name)
- }
- }
-
- return permissions, nil
-}
-
-type SecretInfo struct {
- Organization organization
- OrgPermissions []string
- DBPermissions map[Database][]string
- UnverifiedPermissions []string
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, id, token string) {
- info, err := AnalyzePermissions(cfg, id, token)
- if err != nil {
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- color.Green("[!] Valid PlanetScale credentials\n\n")
- color.Green("[i] Organization: %s\n\n", info.Organization.Name)
- printOrganizationPermissions(info.OrgPermissions)
-
- if len(info.DBPermissions) > 0 {
- printDatabasePermissions(info.DBPermissions)
- }
-
- printUnverifiedPermissions(info.UnverifiedPermissions)
-}
-
-func AnalyzePermissions(cfg *config.Config, id, token string) (*SecretInfo, error) {
- var info = &SecretInfo{}
-
- org, err := getOrganization(cfg, id, token)
- if err != nil {
- return nil, err
- }
- info.Organization = *org
-
- scopes, err := readInScopes()
- if err != nil {
- return nil, fmt.Errorf("reading in scopes: %w", err)
- }
-
- // organization permissions
- orgPermissions, err := getOrganizationPermissions(cfg, scopes, id, token, org.Name)
- if err != nil {
- return nil, err
- }
- info.OrgPermissions = orgPermissions
-
- // database permissions
- dbPermissions, err := getDatabasePermissions(cfg, scopes, id, token, org.Name)
- if err != nil {
- return nil, err
- }
- info.DBPermissions = dbPermissions
-
- // These are permissions that can not be verified,
- // either due to no endpoint available that specifically requires the permission
- // or there does not exist a way to verify these permissions without changing the state of the system (mostly DELETE permissions)
- info.UnverifiedPermissions = []string{
- PermissionStrings[ReadComment],
- PermissionStrings[CreateComment],
- PermissionStrings[ApproveDeployRequest],
- PermissionStrings[DeleteDatabases],
- PermissionStrings[DeleteDatabase],
- PermissionStrings[DeleteOauthTokens],
- PermissionStrings[DeleteBranch],
- PermissionStrings[DeleteBranchPassword],
- PermissionStrings[DeleteProductionBranch],
- PermissionStrings[DeleteProductionBranchPassword],
- PermissionStrings[DeleteBackups],
- PermissionStrings[DeleteProductionBranchBackups],
- PermissionStrings[WriteBackups],
- }
-
- return info, nil
-}
-
-type organization struct {
- Id string `json:"id"`
- Name string `json:"name"`
-}
-
-type organizationJSON struct {
- Data []organization `json:"data"`
-}
-
-func getOrganization(cfg *config.Config, id, key string) (*organization, error) {
- url := "https://api.planetscale.com/v1/organizations"
-
- var organizationJSON organizationJSON
- err := sendGetRequest(cfg, id, key, url, &organizationJSON)
- if err != nil {
- return nil, err
- }
-
- if len(organizationJSON.Data) == 0 {
- return nil, errors.New("invalid api credentials")
- }
-
- return &organizationJSON.Data[0], nil
-}
-
-func getOrganizationPermissions(cfg *config.Config, scopes *Scopes, id, token, orgName string) ([]string, error) {
- organizationPermissions, err := checkPermissions(cfg, scopes.OrganizationScopes, id, token, orgName)
- if err != nil {
- return nil, err
- }
-
- oauthPermissions, err := getOAuthApplicationPermissions(cfg, scopes.OAuthApplicationScopes, id, token, orgName)
- if err != nil {
- return nil, err
- }
- organizationPermissions = append(organizationPermissions, oauthPermissions...)
-
- return organizationPermissions, nil
-}
-
-func getOAuthApplicationPermissions(cfg *config.Config, scopes []Scope, id, key, organization string) ([]string, error) {
- oauthApplicationId, err := getOAuthApplicationId(cfg, id, key, organization)
- if err != nil {
- return nil, err
- }
-
- if oauthApplicationId != "" {
- oauthPermissions, err := checkPermissions(cfg, scopes, id, key, organization, oauthApplicationId)
- if err != nil {
- return nil, err
- }
- return oauthPermissions, nil
- }
- return nil, nil
-}
-
-type oauthApplicationJSON struct {
- Data []struct {
- Id string `json:"id"`
- }
-}
-
-func getOAuthApplicationId(cfg *config.Config, id, key, organization string) (string, error) {
- url := fmt.Sprintf("https://api.planetscale.com/v1/organizations/%s/oauth-applications", organization)
-
- var oauthApplicationJSON oauthApplicationJSON
- err := sendGetRequest(cfg, id, key, url, &oauthApplicationJSON)
- if err != nil {
- return "", err
- }
-
- if len(oauthApplicationJSON.Data) > 0 {
- return oauthApplicationJSON.Data[0].Id, nil
- }
- return "", nil // no oauth application found
-}
-
-func getDatabasePermissions(cfg *config.Config, scopes *Scopes, id, token, orgName string) (map[Database][]string, error) {
- databases, err := getDatabases(cfg, id, token, orgName)
- if err != nil {
- return nil, err
- }
-
- dbPermissionsMap := make(map[Database][]string)
- for _, database := range databases {
- dbPermissions, err := checkPermissions(cfg, scopes.DatabaseScopes, id, token, orgName, database.Name)
- if err != nil {
- return nil, err
- }
- dbPermissionsMap[database] = dbPermissions
-
- branchPermissions, err := getBranchPermissions(cfg, scopes, id, token, orgName, database.Name)
- if err != nil {
- return nil, err
- }
- dbPermissionsMap[database] = append(dbPermissionsMap[database], branchPermissions...)
- }
-
- return dbPermissionsMap, nil
-}
-
-func getBranchPermissions(cfg *config.Config, scopes *Scopes, id, token, orgName, dbName string) ([]string, error) {
- branches, err := getDbBranches(cfg, id, token, orgName, dbName)
- if err != nil {
- return nil, err
- }
-
- // get permissions for prod and non prod branches
- prodDone, nonProdDone := false, false
- allBranchPermissions := make([]string, 0)
- for _, branch := range branches {
- // check if we have already checked permissions for prod or non prod branches
- if (prodDone && branch.Production) || (nonProdDone && !branch.Production) {
- continue
- }
-
- if branch.Production {
- prodDone = true
- } else {
- nonProdDone = true
- }
-
- branchPermissions, err := checkBranchPermissions(cfg, scopes.BranchScopes, id, token, orgName, dbName, branch.Name, branch.Production)
- if err != nil {
- return nil, err
- }
- allBranchPermissions = append(allBranchPermissions, branchPermissions...)
-
- backupId, err := getBackupId(cfg, id, token, orgName, dbName, branch.Name)
- if err != nil {
- return nil, err
- }
-
- if backupId != "" {
- backupPermissions, err := checkBackupPermissions(cfg, scopes.BackupScopes, id, token, orgName, dbName, backupId, branch.Production)
- if err != nil {
- return nil, err
- }
- allBranchPermissions = append(allBranchPermissions, backupPermissions...)
- }
-
- if prodDone && nonProdDone {
- break
- }
- }
-
- return allBranchPermissions, err
-}
-
-type Database struct {
- Id string `json:"id"`
- Name string `json:"name"`
-}
-type databasesJSON struct {
- Data []Database `json:"data"`
- NextPageUrl string `json:"next_page_url"`
-}
-
-func getDatabases(cfg *config.Config, id, key, organization string) ([]Database, error) {
- url := fmt.Sprintf("https://api.planetscale.com/v1/organizations/%s/databases", organization)
- databases := make([]Database, 0)
-
- // loop for pagination
- for url != "" {
- var databasesResponse databasesJSON
- err := sendGetRequest(cfg, id, key, url, &databasesResponse)
- if err != nil {
- return nil, err
- }
-
- databases = append(databases, databasesResponse.Data...)
- url = databasesResponse.NextPageUrl
- }
-
- return databases, nil
-}
-
-type Branch struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Production bool `json:"production"`
-}
-
-type branchesJSON struct {
- Data []Branch `json:"data"`
-}
-
-func getDbBranches(cfg *config.Config, id, key, organization, db string) ([]Branch, error) {
- url := fmt.Sprintf("https://api.planetscale.com/v1/organizations/%s/databases/%s/branches", organization, db)
- var branchesResponse branchesJSON
- err := sendGetRequest(cfg, id, key, url, &branchesResponse)
- if err != nil {
- return nil, err
- }
- return branchesResponse.Data, nil
-}
-
-type backupsJson struct {
- Data []struct {
- Id string `json:"id"`
- }
-}
-
-func getBackupId(cfg *config.Config, id, key, organization, db, branch string) (string, error) {
- url := fmt.Sprintf("https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/backups", organization, db, branch)
- var backupsResponse backupsJson
- err := sendGetRequest(cfg, id, key, url, &backupsResponse)
- if err != nil {
- return "", err
- }
- if len(backupsResponse.Data) > 0 {
- return backupsResponse.Data[0].Id, nil
- }
- return "", nil // no backups found
-}
-
-func sendGetRequest(cfg *config.Config, id, key, url string, responseObj interface{}) error {
- client := analyzers.NewAnalyzeClient(cfg)
-
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- return err
- }
-
- req.Header.Set("Authorization", fmt.Sprintf("%s:%s", id, key))
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
-
- // Check response status code
- switch resp.StatusCode {
- case http.StatusOK:
- // Decode response body
- err = json.NewDecoder(resp.Body).Decode(&responseObj)
- if err != nil {
- return err
- }
- return nil // response successfully decoded
- case http.StatusForbidden:
- return nil // no permission
- default:
- return fmt.Errorf("unexpected status code %d", resp.StatusCode)
- }
-}
-
-func printOrganizationPermissions(permissions []string) {
- color.Yellow("[i] Organization Permissions:")
-
- if len(permissions) == 0 {
- color.Yellow("No permissions found")
- } else {
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission"})
- for _, permission := range permissions {
- t.AppendRow(table.Row{color.GreenString(permission)})
- }
- t.Render()
- }
-}
-
-func printDatabasePermissions(permissions map[Database][]string) {
- color.Yellow("[i] Database Permissions:")
-
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Database", "Permission"})
- for database, dbPermissions := range permissions {
- t.AppendRow(table.Row{database.Name, color.GreenString(strings.Join(dbPermissions, ", "))})
- }
- t.Render()
-}
-
-func printUnverifiedPermissions(permissions []string) {
- color.Yellow("[i] Unverified Permissions:")
-
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission"})
- for _, permission := range permissions {
- t.AppendRow(table.Row{color.YellowString(permission)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/planetscale/planetscale_test.go b/pkg/analyzer/analyzers/planetscale/planetscale_test.go
deleted file mode 100644
index 22da2ce64d26..000000000000
--- a/pkg/analyzer/analyzers/planetscale/planetscale_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package planetscale
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- id string
- token string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid planetscale id and key",
- id: testSecrets.MustGetField("PLANET_SCALE_ID"),
- token: testSecrets.MustGetField("PLANET_SCALE_TOKEN"),
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"id": tt.id, "token": tt.token})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/planetscale/scopes.json b/pkg/analyzer/analyzers/planetscale/scopes.json
deleted file mode 100644
index c17255a8b4e2..000000000000
--- a/pkg/analyzer/analyzers/planetscale/scopes.json
+++ /dev/null
@@ -1,203 +0,0 @@
-{
- "organization_scopes": [
- {
- "name": "read_organization",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "read_invoices",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/invoices",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "read_databases",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "read_audit_logs",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/audit-log",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "create_databases",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "read_oauth_applications",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/oauth-applications",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- }
- ],
- "oauth_application_scopes": [
- {
- "name": "write_oauth_tokens",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/oauth-applications/%s/token",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "read_oauth_tokens",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/oauth-applications/%s/tokens",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- }
- ],
- "database_scopes": [
- {
- "name": "read_database",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "write_database",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s",
- "method": "PATCH",
- "valid_status_code": [400],
- "invalid_status_code": [403],
- "payload": {
- "default_branch": "`nowaythisbranchcanexist"
- }
- }
- },
- {
- "name": "read_branch",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "create_branch",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "read_deploy_request",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/deploy-requests",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- },
- {
- "name": "create_deploy_request",
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/deploy-requests",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403]
- }
- }
- ],
- "branch_scopes": [
- {
- "name": "connect_branch",
- "production": false,
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/passwords",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403],
- "payload": {
- "role": "`nowaythisrolecanexist"
- }
- }
- },
- {
- "name": "connect_production_branch",
- "production": true,
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/passwords",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403],
- "payload": {
- "role": "`nowaythisrolecanexist"
- }
- }
- },
- {
- "name": "read_backups",
- "production": true,
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/backups",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- }
- }
- ],
- "backup_scopes": [
- {
- "name": "restore_backup",
- "production": false,
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403],
- "payload": {
- "backup_id": "%s"
- }
- }
- },
- {
- "name": "restore_production_branch_backup",
- "production": true,
- "test": {
- "endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches",
- "method": "POST",
- "valid_status_code": [422],
- "invalid_status_code": [403],
- "payload": {
- "backup_id": "%s"
- }
- }
- }
- ]
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/postgres/expected_output.json b/pkg/analyzer/analyzers/postgres/expected_output.json
deleted file mode 100644
index f081caed99b4..000000000000
--- a/pkg/analyzer/analyzers/postgres/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":12,"Bindings":[{"Resource":{"Name":"_pg_foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/_pg_foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"_pg_foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/_pg_foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"_pg_foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/_pg_foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"_pg_foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/_pg_foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"_pg_foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/_pg_foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"_pg_foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/_pg_foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"_pg_foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/_pg_foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"_pg_foreign_servers","FullyQualifiedName":"localhost/postgres/_pg_foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"_pg_foreign_servers","FullyQualifiedName":"localhost/postgres/_pg_foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"_pg_foreign_servers","FullyQualifiedName":"localhost/postgres/_pg_foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"_pg_foreign_servers","FullyQualifiedName":"localhost/postgres/_pg_foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"_pg_foreign_servers","FullyQualifiedName":"localhost/postgres/_pg_foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"_pg_foreign_servers","FullyQualifiedName":"localhost/postgres/_pg_foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"_pg_foreign_servers","FullyQualifiedName":"localhost/postgres/_pg_foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"_pg_foreign_table_columns","FullyQualifiedName":"localhost/postgres/_pg_foreign_table_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"_pg_foreign_table_columns","FullyQualifiedName":"localhost/postgres/_pg_foreign_table_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"_pg_foreign_table_columns","FullyQualifiedName":"localhost/postgres/_pg_foreign_table_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"_pg_foreign_table_columns","FullyQualifiedName":"localhost/postgres/_pg_foreign_table_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"_pg_foreign_table_columns","FullyQualifiedName":"localhost/postgres/_pg_foreign_table_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"_pg_foreign_table_columns","FullyQualifiedName":"localhost/postgres/_pg_foreign_table_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"_pg_foreign_table_columns","FullyQualifiedName":"localhost/postgres/_pg_foreign_table_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"_pg_foreign_tables","FullyQualifiedName":"localhost/postgres/_pg_foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"_pg_foreign_tables","FullyQualifiedName":"localhost/postgres/_pg_foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"_pg_foreign_tables","FullyQualifiedName":"localhost/postgres/_pg_foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"_pg_foreign_tables","FullyQualifiedName":"localhost/postgres/_pg_foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"_pg_foreign_tables","FullyQualifiedName":"localhost/postgres/_pg_foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"_pg_foreign_tables","FullyQualifiedName":"localhost/postgres/_pg_foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"_pg_foreign_tables","FullyQualifiedName":"localhost/postgres/_pg_foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"_pg_user_mappings","FullyQualifiedName":"localhost/postgres/_pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"_pg_user_mappings","FullyQualifiedName":"localhost/postgres/_pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"_pg_user_mappings","FullyQualifiedName":"localhost/postgres/_pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"_pg_user_mappings","FullyQualifiedName":"localhost/postgres/_pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"_pg_user_mappings","FullyQualifiedName":"localhost/postgres/_pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"_pg_user_mappings","FullyQualifiedName":"localhost/postgres/_pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"_pg_user_mappings","FullyQualifiedName":"localhost/postgres/_pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"administrable_role_authorizations","FullyQualifiedName":"localhost/postgres/administrable_role_authorizations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"administrable_role_authorizations","FullyQualifiedName":"localhost/postgres/administrable_role_authorizations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"administrable_role_authorizations","FullyQualifiedName":"localhost/postgres/administrable_role_authorizations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"administrable_role_authorizations","FullyQualifiedName":"localhost/postgres/administrable_role_authorizations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"administrable_role_authorizations","FullyQualifiedName":"localhost/postgres/administrable_role_authorizations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"administrable_role_authorizations","FullyQualifiedName":"localhost/postgres/administrable_role_authorizations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"administrable_role_authorizations","FullyQualifiedName":"localhost/postgres/administrable_role_authorizations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"applicable_roles","FullyQualifiedName":"localhost/postgres/applicable_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"applicable_roles","FullyQualifiedName":"localhost/postgres/applicable_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"applicable_roles","FullyQualifiedName":"localhost/postgres/applicable_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"applicable_roles","FullyQualifiedName":"localhost/postgres/applicable_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"applicable_roles","FullyQualifiedName":"localhost/postgres/applicable_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"applicable_roles","FullyQualifiedName":"localhost/postgres/applicable_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"applicable_roles","FullyQualifiedName":"localhost/postgres/applicable_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"attributes","FullyQualifiedName":"localhost/postgres/attributes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"attributes","FullyQualifiedName":"localhost/postgres/attributes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"attributes","FullyQualifiedName":"localhost/postgres/attributes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"attributes","FullyQualifiedName":"localhost/postgres/attributes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"attributes","FullyQualifiedName":"localhost/postgres/attributes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"attributes","FullyQualifiedName":"localhost/postgres/attributes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"attributes","FullyQualifiedName":"localhost/postgres/attributes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"character_sets","FullyQualifiedName":"localhost/postgres/character_sets","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"character_sets","FullyQualifiedName":"localhost/postgres/character_sets","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"character_sets","FullyQualifiedName":"localhost/postgres/character_sets","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"character_sets","FullyQualifiedName":"localhost/postgres/character_sets","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"character_sets","FullyQualifiedName":"localhost/postgres/character_sets","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"character_sets","FullyQualifiedName":"localhost/postgres/character_sets","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"character_sets","FullyQualifiedName":"localhost/postgres/character_sets","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"check_constraint_routine_usage","FullyQualifiedName":"localhost/postgres/check_constraint_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"check_constraint_routine_usage","FullyQualifiedName":"localhost/postgres/check_constraint_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"check_constraint_routine_usage","FullyQualifiedName":"localhost/postgres/check_constraint_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"check_constraint_routine_usage","FullyQualifiedName":"localhost/postgres/check_constraint_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"check_constraint_routine_usage","FullyQualifiedName":"localhost/postgres/check_constraint_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"check_constraint_routine_usage","FullyQualifiedName":"localhost/postgres/check_constraint_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"check_constraint_routine_usage","FullyQualifiedName":"localhost/postgres/check_constraint_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"check_constraints","FullyQualifiedName":"localhost/postgres/check_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"check_constraints","FullyQualifiedName":"localhost/postgres/check_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"check_constraints","FullyQualifiedName":"localhost/postgres/check_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"check_constraints","FullyQualifiedName":"localhost/postgres/check_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"check_constraints","FullyQualifiedName":"localhost/postgres/check_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"check_constraints","FullyQualifiedName":"localhost/postgres/check_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"check_constraints","FullyQualifiedName":"localhost/postgres/check_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"collation_character_set_applicability","FullyQualifiedName":"localhost/postgres/collation_character_set_applicability","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"collation_character_set_applicability","FullyQualifiedName":"localhost/postgres/collation_character_set_applicability","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"collation_character_set_applicability","FullyQualifiedName":"localhost/postgres/collation_character_set_applicability","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"collation_character_set_applicability","FullyQualifiedName":"localhost/postgres/collation_character_set_applicability","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"collation_character_set_applicability","FullyQualifiedName":"localhost/postgres/collation_character_set_applicability","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"collation_character_set_applicability","FullyQualifiedName":"localhost/postgres/collation_character_set_applicability","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"collation_character_set_applicability","FullyQualifiedName":"localhost/postgres/collation_character_set_applicability","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"collations","FullyQualifiedName":"localhost/postgres/collations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"collations","FullyQualifiedName":"localhost/postgres/collations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"collations","FullyQualifiedName":"localhost/postgres/collations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"collations","FullyQualifiedName":"localhost/postgres/collations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"collations","FullyQualifiedName":"localhost/postgres/collations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"collations","FullyQualifiedName":"localhost/postgres/collations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"collations","FullyQualifiedName":"localhost/postgres/collations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"column_column_usage","FullyQualifiedName":"localhost/postgres/column_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"column_column_usage","FullyQualifiedName":"localhost/postgres/column_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"column_column_usage","FullyQualifiedName":"localhost/postgres/column_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"column_column_usage","FullyQualifiedName":"localhost/postgres/column_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"column_column_usage","FullyQualifiedName":"localhost/postgres/column_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"column_column_usage","FullyQualifiedName":"localhost/postgres/column_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"column_column_usage","FullyQualifiedName":"localhost/postgres/column_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"column_domain_usage","FullyQualifiedName":"localhost/postgres/column_domain_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"column_domain_usage","FullyQualifiedName":"localhost/postgres/column_domain_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"column_domain_usage","FullyQualifiedName":"localhost/postgres/column_domain_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"column_domain_usage","FullyQualifiedName":"localhost/postgres/column_domain_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"column_domain_usage","FullyQualifiedName":"localhost/postgres/column_domain_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"column_domain_usage","FullyQualifiedName":"localhost/postgres/column_domain_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"column_domain_usage","FullyQualifiedName":"localhost/postgres/column_domain_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"column_options","FullyQualifiedName":"localhost/postgres/column_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"column_options","FullyQualifiedName":"localhost/postgres/column_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"column_options","FullyQualifiedName":"localhost/postgres/column_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"column_options","FullyQualifiedName":"localhost/postgres/column_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"column_options","FullyQualifiedName":"localhost/postgres/column_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"column_options","FullyQualifiedName":"localhost/postgres/column_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"column_options","FullyQualifiedName":"localhost/postgres/column_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"column_privileges","FullyQualifiedName":"localhost/postgres/column_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"column_privileges","FullyQualifiedName":"localhost/postgres/column_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"column_privileges","FullyQualifiedName":"localhost/postgres/column_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"column_privileges","FullyQualifiedName":"localhost/postgres/column_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"column_privileges","FullyQualifiedName":"localhost/postgres/column_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"column_privileges","FullyQualifiedName":"localhost/postgres/column_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"column_privileges","FullyQualifiedName":"localhost/postgres/column_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"column_udt_usage","FullyQualifiedName":"localhost/postgres/column_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"column_udt_usage","FullyQualifiedName":"localhost/postgres/column_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"column_udt_usage","FullyQualifiedName":"localhost/postgres/column_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"column_udt_usage","FullyQualifiedName":"localhost/postgres/column_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"column_udt_usage","FullyQualifiedName":"localhost/postgres/column_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"column_udt_usage","FullyQualifiedName":"localhost/postgres/column_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"column_udt_usage","FullyQualifiedName":"localhost/postgres/column_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"columns","FullyQualifiedName":"localhost/postgres/columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"columns","FullyQualifiedName":"localhost/postgres/columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"columns","FullyQualifiedName":"localhost/postgres/columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"columns","FullyQualifiedName":"localhost/postgres/columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"columns","FullyQualifiedName":"localhost/postgres/columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"columns","FullyQualifiedName":"localhost/postgres/columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"columns","FullyQualifiedName":"localhost/postgres/columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"constraint_column_usage","FullyQualifiedName":"localhost/postgres/constraint_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"constraint_column_usage","FullyQualifiedName":"localhost/postgres/constraint_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"constraint_column_usage","FullyQualifiedName":"localhost/postgres/constraint_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"constraint_column_usage","FullyQualifiedName":"localhost/postgres/constraint_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"constraint_column_usage","FullyQualifiedName":"localhost/postgres/constraint_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"constraint_column_usage","FullyQualifiedName":"localhost/postgres/constraint_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"constraint_column_usage","FullyQualifiedName":"localhost/postgres/constraint_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"constraint_table_usage","FullyQualifiedName":"localhost/postgres/constraint_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"constraint_table_usage","FullyQualifiedName":"localhost/postgres/constraint_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"constraint_table_usage","FullyQualifiedName":"localhost/postgres/constraint_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"constraint_table_usage","FullyQualifiedName":"localhost/postgres/constraint_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"constraint_table_usage","FullyQualifiedName":"localhost/postgres/constraint_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"constraint_table_usage","FullyQualifiedName":"localhost/postgres/constraint_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"constraint_table_usage","FullyQualifiedName":"localhost/postgres/constraint_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"data_type_privileges","FullyQualifiedName":"localhost/postgres/data_type_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"data_type_privileges","FullyQualifiedName":"localhost/postgres/data_type_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"data_type_privileges","FullyQualifiedName":"localhost/postgres/data_type_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"data_type_privileges","FullyQualifiedName":"localhost/postgres/data_type_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"data_type_privileges","FullyQualifiedName":"localhost/postgres/data_type_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"data_type_privileges","FullyQualifiedName":"localhost/postgres/data_type_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"data_type_privileges","FullyQualifiedName":"localhost/postgres/data_type_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"domain_constraints","FullyQualifiedName":"localhost/postgres/domain_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"domain_constraints","FullyQualifiedName":"localhost/postgres/domain_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"domain_constraints","FullyQualifiedName":"localhost/postgres/domain_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"domain_constraints","FullyQualifiedName":"localhost/postgres/domain_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"domain_constraints","FullyQualifiedName":"localhost/postgres/domain_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"domain_constraints","FullyQualifiedName":"localhost/postgres/domain_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"domain_constraints","FullyQualifiedName":"localhost/postgres/domain_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"domain_udt_usage","FullyQualifiedName":"localhost/postgres/domain_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"domain_udt_usage","FullyQualifiedName":"localhost/postgres/domain_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"domain_udt_usage","FullyQualifiedName":"localhost/postgres/domain_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"domain_udt_usage","FullyQualifiedName":"localhost/postgres/domain_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"domain_udt_usage","FullyQualifiedName":"localhost/postgres/domain_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"domain_udt_usage","FullyQualifiedName":"localhost/postgres/domain_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"domain_udt_usage","FullyQualifiedName":"localhost/postgres/domain_udt_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"domains","FullyQualifiedName":"localhost/postgres/domains","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"domains","FullyQualifiedName":"localhost/postgres/domains","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"domains","FullyQualifiedName":"localhost/postgres/domains","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"domains","FullyQualifiedName":"localhost/postgres/domains","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"domains","FullyQualifiedName":"localhost/postgres/domains","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"domains","FullyQualifiedName":"localhost/postgres/domains","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"domains","FullyQualifiedName":"localhost/postgres/domains","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"element_types","FullyQualifiedName":"localhost/postgres/element_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"element_types","FullyQualifiedName":"localhost/postgres/element_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"element_types","FullyQualifiedName":"localhost/postgres/element_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"element_types","FullyQualifiedName":"localhost/postgres/element_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"element_types","FullyQualifiedName":"localhost/postgres/element_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"element_types","FullyQualifiedName":"localhost/postgres/element_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"element_types","FullyQualifiedName":"localhost/postgres/element_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"enabled_roles","FullyQualifiedName":"localhost/postgres/enabled_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"enabled_roles","FullyQualifiedName":"localhost/postgres/enabled_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"enabled_roles","FullyQualifiedName":"localhost/postgres/enabled_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"enabled_roles","FullyQualifiedName":"localhost/postgres/enabled_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"enabled_roles","FullyQualifiedName":"localhost/postgres/enabled_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"enabled_roles","FullyQualifiedName":"localhost/postgres/enabled_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"enabled_roles","FullyQualifiedName":"localhost/postgres/enabled_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"foreign_data_wrapper_options","FullyQualifiedName":"localhost/postgres/foreign_data_wrapper_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"foreign_data_wrapper_options","FullyQualifiedName":"localhost/postgres/foreign_data_wrapper_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"foreign_data_wrapper_options","FullyQualifiedName":"localhost/postgres/foreign_data_wrapper_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"foreign_data_wrapper_options","FullyQualifiedName":"localhost/postgres/foreign_data_wrapper_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"foreign_data_wrapper_options","FullyQualifiedName":"localhost/postgres/foreign_data_wrapper_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"foreign_data_wrapper_options","FullyQualifiedName":"localhost/postgres/foreign_data_wrapper_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"foreign_data_wrapper_options","FullyQualifiedName":"localhost/postgres/foreign_data_wrapper_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"foreign_data_wrappers","FullyQualifiedName":"localhost/postgres/foreign_data_wrappers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"foreign_server_options","FullyQualifiedName":"localhost/postgres/foreign_server_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"foreign_server_options","FullyQualifiedName":"localhost/postgres/foreign_server_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"foreign_server_options","FullyQualifiedName":"localhost/postgres/foreign_server_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"foreign_server_options","FullyQualifiedName":"localhost/postgres/foreign_server_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"foreign_server_options","FullyQualifiedName":"localhost/postgres/foreign_server_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"foreign_server_options","FullyQualifiedName":"localhost/postgres/foreign_server_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"foreign_server_options","FullyQualifiedName":"localhost/postgres/foreign_server_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"foreign_servers","FullyQualifiedName":"localhost/postgres/foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"foreign_servers","FullyQualifiedName":"localhost/postgres/foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"foreign_servers","FullyQualifiedName":"localhost/postgres/foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"foreign_servers","FullyQualifiedName":"localhost/postgres/foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"foreign_servers","FullyQualifiedName":"localhost/postgres/foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"foreign_servers","FullyQualifiedName":"localhost/postgres/foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"foreign_servers","FullyQualifiedName":"localhost/postgres/foreign_servers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"foreign_table_options","FullyQualifiedName":"localhost/postgres/foreign_table_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"foreign_table_options","FullyQualifiedName":"localhost/postgres/foreign_table_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"foreign_table_options","FullyQualifiedName":"localhost/postgres/foreign_table_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"foreign_table_options","FullyQualifiedName":"localhost/postgres/foreign_table_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"foreign_table_options","FullyQualifiedName":"localhost/postgres/foreign_table_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"foreign_table_options","FullyQualifiedName":"localhost/postgres/foreign_table_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"foreign_table_options","FullyQualifiedName":"localhost/postgres/foreign_table_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"foreign_tables","FullyQualifiedName":"localhost/postgres/foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"foreign_tables","FullyQualifiedName":"localhost/postgres/foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"foreign_tables","FullyQualifiedName":"localhost/postgres/foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"foreign_tables","FullyQualifiedName":"localhost/postgres/foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"foreign_tables","FullyQualifiedName":"localhost/postgres/foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"foreign_tables","FullyQualifiedName":"localhost/postgres/foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"foreign_tables","FullyQualifiedName":"localhost/postgres/foreign_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"information_schema_catalog_name","FullyQualifiedName":"localhost/postgres/information_schema_catalog_name","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"information_schema_catalog_name","FullyQualifiedName":"localhost/postgres/information_schema_catalog_name","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"information_schema_catalog_name","FullyQualifiedName":"localhost/postgres/information_schema_catalog_name","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"information_schema_catalog_name","FullyQualifiedName":"localhost/postgres/information_schema_catalog_name","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"information_schema_catalog_name","FullyQualifiedName":"localhost/postgres/information_schema_catalog_name","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"information_schema_catalog_name","FullyQualifiedName":"localhost/postgres/information_schema_catalog_name","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"information_schema_catalog_name","FullyQualifiedName":"localhost/postgres/information_schema_catalog_name","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"key_column_usage","FullyQualifiedName":"localhost/postgres/key_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"key_column_usage","FullyQualifiedName":"localhost/postgres/key_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"key_column_usage","FullyQualifiedName":"localhost/postgres/key_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"key_column_usage","FullyQualifiedName":"localhost/postgres/key_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"key_column_usage","FullyQualifiedName":"localhost/postgres/key_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"key_column_usage","FullyQualifiedName":"localhost/postgres/key_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"key_column_usage","FullyQualifiedName":"localhost/postgres/key_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"parameters","FullyQualifiedName":"localhost/postgres/parameters","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"parameters","FullyQualifiedName":"localhost/postgres/parameters","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"parameters","FullyQualifiedName":"localhost/postgres/parameters","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"parameters","FullyQualifiedName":"localhost/postgres/parameters","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"parameters","FullyQualifiedName":"localhost/postgres/parameters","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"parameters","FullyQualifiedName":"localhost/postgres/parameters","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"parameters","FullyQualifiedName":"localhost/postgres/parameters","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_aggregate","FullyQualifiedName":"localhost/postgres/pg_aggregate","Type":"table","Metadata":{"rows":"157","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_aggregate","FullyQualifiedName":"localhost/postgres/pg_aggregate","Type":"table","Metadata":{"rows":"157","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_aggregate","FullyQualifiedName":"localhost/postgres/pg_aggregate","Type":"table","Metadata":{"rows":"157","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_aggregate","FullyQualifiedName":"localhost/postgres/pg_aggregate","Type":"table","Metadata":{"rows":"157","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_aggregate","FullyQualifiedName":"localhost/postgres/pg_aggregate","Type":"table","Metadata":{"rows":"157","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_aggregate","FullyQualifiedName":"localhost/postgres/pg_aggregate","Type":"table","Metadata":{"rows":"157","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_aggregate","FullyQualifiedName":"localhost/postgres/pg_aggregate","Type":"table","Metadata":{"rows":"157","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_am","FullyQualifiedName":"localhost/postgres/pg_am","Type":"table","Metadata":{"rows":"7","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_am","FullyQualifiedName":"localhost/postgres/pg_am","Type":"table","Metadata":{"rows":"7","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_am","FullyQualifiedName":"localhost/postgres/pg_am","Type":"table","Metadata":{"rows":"7","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_am","FullyQualifiedName":"localhost/postgres/pg_am","Type":"table","Metadata":{"rows":"7","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_am","FullyQualifiedName":"localhost/postgres/pg_am","Type":"table","Metadata":{"rows":"7","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_am","FullyQualifiedName":"localhost/postgres/pg_am","Type":"table","Metadata":{"rows":"7","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_am","FullyQualifiedName":"localhost/postgres/pg_am","Type":"table","Metadata":{"rows":"7","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_amop","FullyQualifiedName":"localhost/postgres/pg_amop","Type":"table","Metadata":{"rows":"945","size":"224 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_amop","FullyQualifiedName":"localhost/postgres/pg_amop","Type":"table","Metadata":{"rows":"945","size":"224 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_amop","FullyQualifiedName":"localhost/postgres/pg_amop","Type":"table","Metadata":{"rows":"945","size":"224 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_amop","FullyQualifiedName":"localhost/postgres/pg_amop","Type":"table","Metadata":{"rows":"945","size":"224 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_amop","FullyQualifiedName":"localhost/postgres/pg_amop","Type":"table","Metadata":{"rows":"945","size":"224 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_amop","FullyQualifiedName":"localhost/postgres/pg_amop","Type":"table","Metadata":{"rows":"945","size":"224 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_amop","FullyQualifiedName":"localhost/postgres/pg_amop","Type":"table","Metadata":{"rows":"945","size":"224 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_amproc","FullyQualifiedName":"localhost/postgres/pg_amproc","Type":"table","Metadata":{"rows":"696","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_amproc","FullyQualifiedName":"localhost/postgres/pg_amproc","Type":"table","Metadata":{"rows":"696","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_amproc","FullyQualifiedName":"localhost/postgres/pg_amproc","Type":"table","Metadata":{"rows":"696","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_amproc","FullyQualifiedName":"localhost/postgres/pg_amproc","Type":"table","Metadata":{"rows":"696","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_amproc","FullyQualifiedName":"localhost/postgres/pg_amproc","Type":"table","Metadata":{"rows":"696","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_amproc","FullyQualifiedName":"localhost/postgres/pg_amproc","Type":"table","Metadata":{"rows":"696","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_amproc","FullyQualifiedName":"localhost/postgres/pg_amproc","Type":"table","Metadata":{"rows":"696","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_attrdef","FullyQualifiedName":"localhost/postgres/pg_attrdef","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_attrdef","FullyQualifiedName":"localhost/postgres/pg_attrdef","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_attrdef","FullyQualifiedName":"localhost/postgres/pg_attrdef","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_attrdef","FullyQualifiedName":"localhost/postgres/pg_attrdef","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_attrdef","FullyQualifiedName":"localhost/postgres/pg_attrdef","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_attrdef","FullyQualifiedName":"localhost/postgres/pg_attrdef","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_attrdef","FullyQualifiedName":"localhost/postgres/pg_attrdef","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_attribute","FullyQualifiedName":"localhost/postgres/pg_attribute","Type":"table","Metadata":{"rows":"3108","size":"704 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_attribute","FullyQualifiedName":"localhost/postgres/pg_attribute","Type":"table","Metadata":{"rows":"3108","size":"704 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_attribute","FullyQualifiedName":"localhost/postgres/pg_attribute","Type":"table","Metadata":{"rows":"3108","size":"704 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_attribute","FullyQualifiedName":"localhost/postgres/pg_attribute","Type":"table","Metadata":{"rows":"3108","size":"704 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_attribute","FullyQualifiedName":"localhost/postgres/pg_attribute","Type":"table","Metadata":{"rows":"3108","size":"704 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_attribute","FullyQualifiedName":"localhost/postgres/pg_attribute","Type":"table","Metadata":{"rows":"3108","size":"704 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_attribute","FullyQualifiedName":"localhost/postgres/pg_attribute","Type":"table","Metadata":{"rows":"3108","size":"704 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_auth_members","FullyQualifiedName":"localhost/postgres/pg_auth_members","Type":"table","Metadata":{"rows":"3","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_auth_members","FullyQualifiedName":"localhost/postgres/pg_auth_members","Type":"table","Metadata":{"rows":"3","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_auth_members","FullyQualifiedName":"localhost/postgres/pg_auth_members","Type":"table","Metadata":{"rows":"3","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_auth_members","FullyQualifiedName":"localhost/postgres/pg_auth_members","Type":"table","Metadata":{"rows":"3","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_auth_members","FullyQualifiedName":"localhost/postgres/pg_auth_members","Type":"table","Metadata":{"rows":"3","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_auth_members","FullyQualifiedName":"localhost/postgres/pg_auth_members","Type":"table","Metadata":{"rows":"3","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_auth_members","FullyQualifiedName":"localhost/postgres/pg_auth_members","Type":"table","Metadata":{"rows":"3","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_authid","FullyQualifiedName":"localhost/postgres/pg_authid","Type":"table","Metadata":{"rows":"15","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_authid","FullyQualifiedName":"localhost/postgres/pg_authid","Type":"table","Metadata":{"rows":"15","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_authid","FullyQualifiedName":"localhost/postgres/pg_authid","Type":"table","Metadata":{"rows":"15","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_authid","FullyQualifiedName":"localhost/postgres/pg_authid","Type":"table","Metadata":{"rows":"15","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_authid","FullyQualifiedName":"localhost/postgres/pg_authid","Type":"table","Metadata":{"rows":"15","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_authid","FullyQualifiedName":"localhost/postgres/pg_authid","Type":"table","Metadata":{"rows":"15","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_authid","FullyQualifiedName":"localhost/postgres/pg_authid","Type":"table","Metadata":{"rows":"15","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_available_extension_versions","FullyQualifiedName":"localhost/postgres/pg_available_extension_versions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_available_extension_versions","FullyQualifiedName":"localhost/postgres/pg_available_extension_versions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_available_extension_versions","FullyQualifiedName":"localhost/postgres/pg_available_extension_versions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_available_extension_versions","FullyQualifiedName":"localhost/postgres/pg_available_extension_versions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_available_extension_versions","FullyQualifiedName":"localhost/postgres/pg_available_extension_versions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_available_extension_versions","FullyQualifiedName":"localhost/postgres/pg_available_extension_versions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_available_extension_versions","FullyQualifiedName":"localhost/postgres/pg_available_extension_versions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_available_extensions","FullyQualifiedName":"localhost/postgres/pg_available_extensions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_available_extensions","FullyQualifiedName":"localhost/postgres/pg_available_extensions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_available_extensions","FullyQualifiedName":"localhost/postgres/pg_available_extensions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_available_extensions","FullyQualifiedName":"localhost/postgres/pg_available_extensions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_available_extensions","FullyQualifiedName":"localhost/postgres/pg_available_extensions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_available_extensions","FullyQualifiedName":"localhost/postgres/pg_available_extensions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_available_extensions","FullyQualifiedName":"localhost/postgres/pg_available_extensions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_backend_memory_contexts","FullyQualifiedName":"localhost/postgres/pg_backend_memory_contexts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_backend_memory_contexts","FullyQualifiedName":"localhost/postgres/pg_backend_memory_contexts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_backend_memory_contexts","FullyQualifiedName":"localhost/postgres/pg_backend_memory_contexts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_backend_memory_contexts","FullyQualifiedName":"localhost/postgres/pg_backend_memory_contexts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_backend_memory_contexts","FullyQualifiedName":"localhost/postgres/pg_backend_memory_contexts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_backend_memory_contexts","FullyQualifiedName":"localhost/postgres/pg_backend_memory_contexts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_backend_memory_contexts","FullyQualifiedName":"localhost/postgres/pg_backend_memory_contexts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_cast","FullyQualifiedName":"localhost/postgres/pg_cast","Type":"table","Metadata":{"rows":"229","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_cast","FullyQualifiedName":"localhost/postgres/pg_cast","Type":"table","Metadata":{"rows":"229","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_cast","FullyQualifiedName":"localhost/postgres/pg_cast","Type":"table","Metadata":{"rows":"229","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_cast","FullyQualifiedName":"localhost/postgres/pg_cast","Type":"table","Metadata":{"rows":"229","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_cast","FullyQualifiedName":"localhost/postgres/pg_cast","Type":"table","Metadata":{"rows":"229","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_cast","FullyQualifiedName":"localhost/postgres/pg_cast","Type":"table","Metadata":{"rows":"229","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_cast","FullyQualifiedName":"localhost/postgres/pg_cast","Type":"table","Metadata":{"rows":"229","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_class","FullyQualifiedName":"localhost/postgres/pg_class","Type":"table","Metadata":{"rows":"413","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_class","FullyQualifiedName":"localhost/postgres/pg_class","Type":"table","Metadata":{"rows":"413","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_class","FullyQualifiedName":"localhost/postgres/pg_class","Type":"table","Metadata":{"rows":"413","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_class","FullyQualifiedName":"localhost/postgres/pg_class","Type":"table","Metadata":{"rows":"413","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_class","FullyQualifiedName":"localhost/postgres/pg_class","Type":"table","Metadata":{"rows":"413","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_class","FullyQualifiedName":"localhost/postgres/pg_class","Type":"table","Metadata":{"rows":"413","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_class","FullyQualifiedName":"localhost/postgres/pg_class","Type":"table","Metadata":{"rows":"413","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_collation","FullyQualifiedName":"localhost/postgres/pg_collation","Type":"table","Metadata":{"rows":"814","size":"240 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_collation","FullyQualifiedName":"localhost/postgres/pg_collation","Type":"table","Metadata":{"rows":"814","size":"240 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_collation","FullyQualifiedName":"localhost/postgres/pg_collation","Type":"table","Metadata":{"rows":"814","size":"240 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_collation","FullyQualifiedName":"localhost/postgres/pg_collation","Type":"table","Metadata":{"rows":"814","size":"240 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_collation","FullyQualifiedName":"localhost/postgres/pg_collation","Type":"table","Metadata":{"rows":"814","size":"240 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_collation","FullyQualifiedName":"localhost/postgres/pg_collation","Type":"table","Metadata":{"rows":"814","size":"240 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_collation","FullyQualifiedName":"localhost/postgres/pg_collation","Type":"table","Metadata":{"rows":"814","size":"240 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_config","FullyQualifiedName":"localhost/postgres/pg_config","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_config","FullyQualifiedName":"localhost/postgres/pg_config","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_config","FullyQualifiedName":"localhost/postgres/pg_config","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_config","FullyQualifiedName":"localhost/postgres/pg_config","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_config","FullyQualifiedName":"localhost/postgres/pg_config","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_config","FullyQualifiedName":"localhost/postgres/pg_config","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_config","FullyQualifiedName":"localhost/postgres/pg_config","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_constraint","FullyQualifiedName":"localhost/postgres/pg_constraint","Type":"table","Metadata":{"rows":"112","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_constraint","FullyQualifiedName":"localhost/postgres/pg_constraint","Type":"table","Metadata":{"rows":"112","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_constraint","FullyQualifiedName":"localhost/postgres/pg_constraint","Type":"table","Metadata":{"rows":"112","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_constraint","FullyQualifiedName":"localhost/postgres/pg_constraint","Type":"table","Metadata":{"rows":"112","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_constraint","FullyQualifiedName":"localhost/postgres/pg_constraint","Type":"table","Metadata":{"rows":"112","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_constraint","FullyQualifiedName":"localhost/postgres/pg_constraint","Type":"table","Metadata":{"rows":"112","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_constraint","FullyQualifiedName":"localhost/postgres/pg_constraint","Type":"table","Metadata":{"rows":"112","size":"144 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_conversion","FullyQualifiedName":"localhost/postgres/pg_conversion","Type":"table","Metadata":{"rows":"128","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_conversion","FullyQualifiedName":"localhost/postgres/pg_conversion","Type":"table","Metadata":{"rows":"128","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_conversion","FullyQualifiedName":"localhost/postgres/pg_conversion","Type":"table","Metadata":{"rows":"128","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_conversion","FullyQualifiedName":"localhost/postgres/pg_conversion","Type":"table","Metadata":{"rows":"128","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_conversion","FullyQualifiedName":"localhost/postgres/pg_conversion","Type":"table","Metadata":{"rows":"128","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_conversion","FullyQualifiedName":"localhost/postgres/pg_conversion","Type":"table","Metadata":{"rows":"128","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_conversion","FullyQualifiedName":"localhost/postgres/pg_conversion","Type":"table","Metadata":{"rows":"128","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_cursors","FullyQualifiedName":"localhost/postgres/pg_cursors","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_cursors","FullyQualifiedName":"localhost/postgres/pg_cursors","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_cursors","FullyQualifiedName":"localhost/postgres/pg_cursors","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_cursors","FullyQualifiedName":"localhost/postgres/pg_cursors","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_cursors","FullyQualifiedName":"localhost/postgres/pg_cursors","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_cursors","FullyQualifiedName":"localhost/postgres/pg_cursors","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_cursors","FullyQualifiedName":"localhost/postgres/pg_cursors","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_database","FullyQualifiedName":"localhost/postgres/pg_database","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_database","FullyQualifiedName":"localhost/postgres/pg_database","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_database","FullyQualifiedName":"localhost/postgres/pg_database","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_database","FullyQualifiedName":"localhost/postgres/pg_database","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_database","FullyQualifiedName":"localhost/postgres/pg_database","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_database","FullyQualifiedName":"localhost/postgres/pg_database","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_database","FullyQualifiedName":"localhost/postgres/pg_database","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_db_role_setting","FullyQualifiedName":"localhost/postgres/pg_db_role_setting","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_db_role_setting","FullyQualifiedName":"localhost/postgres/pg_db_role_setting","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_db_role_setting","FullyQualifiedName":"localhost/postgres/pg_db_role_setting","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_db_role_setting","FullyQualifiedName":"localhost/postgres/pg_db_role_setting","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_db_role_setting","FullyQualifiedName":"localhost/postgres/pg_db_role_setting","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_db_role_setting","FullyQualifiedName":"localhost/postgres/pg_db_role_setting","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_db_role_setting","FullyQualifiedName":"localhost/postgres/pg_db_role_setting","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_default_acl","FullyQualifiedName":"localhost/postgres/pg_default_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_default_acl","FullyQualifiedName":"localhost/postgres/pg_default_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_default_acl","FullyQualifiedName":"localhost/postgres/pg_default_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_default_acl","FullyQualifiedName":"localhost/postgres/pg_default_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_default_acl","FullyQualifiedName":"localhost/postgres/pg_default_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_default_acl","FullyQualifiedName":"localhost/postgres/pg_default_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_default_acl","FullyQualifiedName":"localhost/postgres/pg_default_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_depend","FullyQualifiedName":"localhost/postgres/pg_depend","Type":"table","Metadata":{"rows":"1704","size":"272 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_depend","FullyQualifiedName":"localhost/postgres/pg_depend","Type":"table","Metadata":{"rows":"1704","size":"272 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_depend","FullyQualifiedName":"localhost/postgres/pg_depend","Type":"table","Metadata":{"rows":"1704","size":"272 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_depend","FullyQualifiedName":"localhost/postgres/pg_depend","Type":"table","Metadata":{"rows":"1704","size":"272 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_depend","FullyQualifiedName":"localhost/postgres/pg_depend","Type":"table","Metadata":{"rows":"1704","size":"272 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_depend","FullyQualifiedName":"localhost/postgres/pg_depend","Type":"table","Metadata":{"rows":"1704","size":"272 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_depend","FullyQualifiedName":"localhost/postgres/pg_depend","Type":"table","Metadata":{"rows":"1704","size":"272 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_description","FullyQualifiedName":"localhost/postgres/pg_description","Type":"table","Metadata":{"rows":"5191","size":"616 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_description","FullyQualifiedName":"localhost/postgres/pg_description","Type":"table","Metadata":{"rows":"5191","size":"616 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_description","FullyQualifiedName":"localhost/postgres/pg_description","Type":"table","Metadata":{"rows":"5191","size":"616 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_description","FullyQualifiedName":"localhost/postgres/pg_description","Type":"table","Metadata":{"rows":"5191","size":"616 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_description","FullyQualifiedName":"localhost/postgres/pg_description","Type":"table","Metadata":{"rows":"5191","size":"616 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_description","FullyQualifiedName":"localhost/postgres/pg_description","Type":"table","Metadata":{"rows":"5191","size":"616 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_description","FullyQualifiedName":"localhost/postgres/pg_description","Type":"table","Metadata":{"rows":"5191","size":"616 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_enum","FullyQualifiedName":"localhost/postgres/pg_enum","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_enum","FullyQualifiedName":"localhost/postgres/pg_enum","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_enum","FullyQualifiedName":"localhost/postgres/pg_enum","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_enum","FullyQualifiedName":"localhost/postgres/pg_enum","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_enum","FullyQualifiedName":"localhost/postgres/pg_enum","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_enum","FullyQualifiedName":"localhost/postgres/pg_enum","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_enum","FullyQualifiedName":"localhost/postgres/pg_enum","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_event_trigger","FullyQualifiedName":"localhost/postgres/pg_event_trigger","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_event_trigger","FullyQualifiedName":"localhost/postgres/pg_event_trigger","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_event_trigger","FullyQualifiedName":"localhost/postgres/pg_event_trigger","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_event_trigger","FullyQualifiedName":"localhost/postgres/pg_event_trigger","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_event_trigger","FullyQualifiedName":"localhost/postgres/pg_event_trigger","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_event_trigger","FullyQualifiedName":"localhost/postgres/pg_event_trigger","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_event_trigger","FullyQualifiedName":"localhost/postgres/pg_event_trigger","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_extension","FullyQualifiedName":"localhost/postgres/pg_extension","Type":"table","Metadata":{"rows":"1","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_extension","FullyQualifiedName":"localhost/postgres/pg_extension","Type":"table","Metadata":{"rows":"1","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_extension","FullyQualifiedName":"localhost/postgres/pg_extension","Type":"table","Metadata":{"rows":"1","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_extension","FullyQualifiedName":"localhost/postgres/pg_extension","Type":"table","Metadata":{"rows":"1","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_extension","FullyQualifiedName":"localhost/postgres/pg_extension","Type":"table","Metadata":{"rows":"1","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_extension","FullyQualifiedName":"localhost/postgres/pg_extension","Type":"table","Metadata":{"rows":"1","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_extension","FullyQualifiedName":"localhost/postgres/pg_extension","Type":"table","Metadata":{"rows":"1","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_file_settings","FullyQualifiedName":"localhost/postgres/pg_file_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_file_settings","FullyQualifiedName":"localhost/postgres/pg_file_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_file_settings","FullyQualifiedName":"localhost/postgres/pg_file_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_file_settings","FullyQualifiedName":"localhost/postgres/pg_file_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_file_settings","FullyQualifiedName":"localhost/postgres/pg_file_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_file_settings","FullyQualifiedName":"localhost/postgres/pg_file_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_file_settings","FullyQualifiedName":"localhost/postgres/pg_file_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_foreign_data_wrapper","FullyQualifiedName":"localhost/postgres/pg_foreign_data_wrapper","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_foreign_data_wrapper","FullyQualifiedName":"localhost/postgres/pg_foreign_data_wrapper","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_foreign_data_wrapper","FullyQualifiedName":"localhost/postgres/pg_foreign_data_wrapper","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_foreign_data_wrapper","FullyQualifiedName":"localhost/postgres/pg_foreign_data_wrapper","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_foreign_data_wrapper","FullyQualifiedName":"localhost/postgres/pg_foreign_data_wrapper","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_foreign_data_wrapper","FullyQualifiedName":"localhost/postgres/pg_foreign_data_wrapper","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_foreign_data_wrapper","FullyQualifiedName":"localhost/postgres/pg_foreign_data_wrapper","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_foreign_server","FullyQualifiedName":"localhost/postgres/pg_foreign_server","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_foreign_server","FullyQualifiedName":"localhost/postgres/pg_foreign_server","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_foreign_server","FullyQualifiedName":"localhost/postgres/pg_foreign_server","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_foreign_server","FullyQualifiedName":"localhost/postgres/pg_foreign_server","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_foreign_server","FullyQualifiedName":"localhost/postgres/pg_foreign_server","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_foreign_server","FullyQualifiedName":"localhost/postgres/pg_foreign_server","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_foreign_server","FullyQualifiedName":"localhost/postgres/pg_foreign_server","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_foreign_table","FullyQualifiedName":"localhost/postgres/pg_foreign_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_foreign_table","FullyQualifiedName":"localhost/postgres/pg_foreign_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_foreign_table","FullyQualifiedName":"localhost/postgres/pg_foreign_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_foreign_table","FullyQualifiedName":"localhost/postgres/pg_foreign_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_foreign_table","FullyQualifiedName":"localhost/postgres/pg_foreign_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_foreign_table","FullyQualifiedName":"localhost/postgres/pg_foreign_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_foreign_table","FullyQualifiedName":"localhost/postgres/pg_foreign_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_group","FullyQualifiedName":"localhost/postgres/pg_group","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_group","FullyQualifiedName":"localhost/postgres/pg_group","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_group","FullyQualifiedName":"localhost/postgres/pg_group","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_group","FullyQualifiedName":"localhost/postgres/pg_group","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_group","FullyQualifiedName":"localhost/postgres/pg_group","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_group","FullyQualifiedName":"localhost/postgres/pg_group","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_group","FullyQualifiedName":"localhost/postgres/pg_group","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_hba_file_rules","FullyQualifiedName":"localhost/postgres/pg_hba_file_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_hba_file_rules","FullyQualifiedName":"localhost/postgres/pg_hba_file_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_hba_file_rules","FullyQualifiedName":"localhost/postgres/pg_hba_file_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_hba_file_rules","FullyQualifiedName":"localhost/postgres/pg_hba_file_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_hba_file_rules","FullyQualifiedName":"localhost/postgres/pg_hba_file_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_hba_file_rules","FullyQualifiedName":"localhost/postgres/pg_hba_file_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_hba_file_rules","FullyQualifiedName":"localhost/postgres/pg_hba_file_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_ident_file_mappings","FullyQualifiedName":"localhost/postgres/pg_ident_file_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_ident_file_mappings","FullyQualifiedName":"localhost/postgres/pg_ident_file_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_ident_file_mappings","FullyQualifiedName":"localhost/postgres/pg_ident_file_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_ident_file_mappings","FullyQualifiedName":"localhost/postgres/pg_ident_file_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_ident_file_mappings","FullyQualifiedName":"localhost/postgres/pg_ident_file_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_ident_file_mappings","FullyQualifiedName":"localhost/postgres/pg_ident_file_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_ident_file_mappings","FullyQualifiedName":"localhost/postgres/pg_ident_file_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_index","FullyQualifiedName":"localhost/postgres/pg_index","Type":"table","Metadata":{"rows":"164","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_index","FullyQualifiedName":"localhost/postgres/pg_index","Type":"table","Metadata":{"rows":"164","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_index","FullyQualifiedName":"localhost/postgres/pg_index","Type":"table","Metadata":{"rows":"164","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_index","FullyQualifiedName":"localhost/postgres/pg_index","Type":"table","Metadata":{"rows":"164","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_index","FullyQualifiedName":"localhost/postgres/pg_index","Type":"table","Metadata":{"rows":"164","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_index","FullyQualifiedName":"localhost/postgres/pg_index","Type":"table","Metadata":{"rows":"164","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_index","FullyQualifiedName":"localhost/postgres/pg_index","Type":"table","Metadata":{"rows":"164","size":"96 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_indexes","FullyQualifiedName":"localhost/postgres/pg_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_indexes","FullyQualifiedName":"localhost/postgres/pg_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_indexes","FullyQualifiedName":"localhost/postgres/pg_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_indexes","FullyQualifiedName":"localhost/postgres/pg_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_indexes","FullyQualifiedName":"localhost/postgres/pg_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_indexes","FullyQualifiedName":"localhost/postgres/pg_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_indexes","FullyQualifiedName":"localhost/postgres/pg_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_inherits","FullyQualifiedName":"localhost/postgres/pg_inherits","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_inherits","FullyQualifiedName":"localhost/postgres/pg_inherits","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_inherits","FullyQualifiedName":"localhost/postgres/pg_inherits","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_inherits","FullyQualifiedName":"localhost/postgres/pg_inherits","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_inherits","FullyQualifiedName":"localhost/postgres/pg_inherits","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_inherits","FullyQualifiedName":"localhost/postgres/pg_inherits","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_inherits","FullyQualifiedName":"localhost/postgres/pg_inherits","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_init_privs","FullyQualifiedName":"localhost/postgres/pg_init_privs","Type":"table","Metadata":{"rows":"220","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_init_privs","FullyQualifiedName":"localhost/postgres/pg_init_privs","Type":"table","Metadata":{"rows":"220","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_init_privs","FullyQualifiedName":"localhost/postgres/pg_init_privs","Type":"table","Metadata":{"rows":"220","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_init_privs","FullyQualifiedName":"localhost/postgres/pg_init_privs","Type":"table","Metadata":{"rows":"220","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_init_privs","FullyQualifiedName":"localhost/postgres/pg_init_privs","Type":"table","Metadata":{"rows":"220","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_init_privs","FullyQualifiedName":"localhost/postgres/pg_init_privs","Type":"table","Metadata":{"rows":"220","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_init_privs","FullyQualifiedName":"localhost/postgres/pg_init_privs","Type":"table","Metadata":{"rows":"220","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_language","FullyQualifiedName":"localhost/postgres/pg_language","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_language","FullyQualifiedName":"localhost/postgres/pg_language","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_language","FullyQualifiedName":"localhost/postgres/pg_language","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_language","FullyQualifiedName":"localhost/postgres/pg_language","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_language","FullyQualifiedName":"localhost/postgres/pg_language","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_language","FullyQualifiedName":"localhost/postgres/pg_language","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_language","FullyQualifiedName":"localhost/postgres/pg_language","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_largeobject","FullyQualifiedName":"localhost/postgres/pg_largeobject","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_largeobject","FullyQualifiedName":"localhost/postgres/pg_largeobject","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_largeobject","FullyQualifiedName":"localhost/postgres/pg_largeobject","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_largeobject","FullyQualifiedName":"localhost/postgres/pg_largeobject","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_largeobject","FullyQualifiedName":"localhost/postgres/pg_largeobject","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_largeobject","FullyQualifiedName":"localhost/postgres/pg_largeobject","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_largeobject","FullyQualifiedName":"localhost/postgres/pg_largeobject","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_largeobject_metadata","FullyQualifiedName":"localhost/postgres/pg_largeobject_metadata","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_largeobject_metadata","FullyQualifiedName":"localhost/postgres/pg_largeobject_metadata","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_largeobject_metadata","FullyQualifiedName":"localhost/postgres/pg_largeobject_metadata","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_largeobject_metadata","FullyQualifiedName":"localhost/postgres/pg_largeobject_metadata","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_largeobject_metadata","FullyQualifiedName":"localhost/postgres/pg_largeobject_metadata","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_largeobject_metadata","FullyQualifiedName":"localhost/postgres/pg_largeobject_metadata","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_largeobject_metadata","FullyQualifiedName":"localhost/postgres/pg_largeobject_metadata","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_locks","FullyQualifiedName":"localhost/postgres/pg_locks","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_locks","FullyQualifiedName":"localhost/postgres/pg_locks","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_locks","FullyQualifiedName":"localhost/postgres/pg_locks","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_locks","FullyQualifiedName":"localhost/postgres/pg_locks","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_locks","FullyQualifiedName":"localhost/postgres/pg_locks","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_locks","FullyQualifiedName":"localhost/postgres/pg_locks","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_locks","FullyQualifiedName":"localhost/postgres/pg_locks","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_matviews","FullyQualifiedName":"localhost/postgres/pg_matviews","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_matviews","FullyQualifiedName":"localhost/postgres/pg_matviews","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_matviews","FullyQualifiedName":"localhost/postgres/pg_matviews","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_matviews","FullyQualifiedName":"localhost/postgres/pg_matviews","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_matviews","FullyQualifiedName":"localhost/postgres/pg_matviews","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_matviews","FullyQualifiedName":"localhost/postgres/pg_matviews","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_matviews","FullyQualifiedName":"localhost/postgres/pg_matviews","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_namespace","FullyQualifiedName":"localhost/postgres/pg_namespace","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_namespace","FullyQualifiedName":"localhost/postgres/pg_namespace","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_namespace","FullyQualifiedName":"localhost/postgres/pg_namespace","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_namespace","FullyQualifiedName":"localhost/postgres/pg_namespace","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_namespace","FullyQualifiedName":"localhost/postgres/pg_namespace","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_namespace","FullyQualifiedName":"localhost/postgres/pg_namespace","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_namespace","FullyQualifiedName":"localhost/postgres/pg_namespace","Type":"table","Metadata":{"rows":"4","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_opclass","FullyQualifiedName":"localhost/postgres/pg_opclass","Type":"table","Metadata":{"rows":"177","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_opclass","FullyQualifiedName":"localhost/postgres/pg_opclass","Type":"table","Metadata":{"rows":"177","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_opclass","FullyQualifiedName":"localhost/postgres/pg_opclass","Type":"table","Metadata":{"rows":"177","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_opclass","FullyQualifiedName":"localhost/postgres/pg_opclass","Type":"table","Metadata":{"rows":"177","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_opclass","FullyQualifiedName":"localhost/postgres/pg_opclass","Type":"table","Metadata":{"rows":"177","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_opclass","FullyQualifiedName":"localhost/postgres/pg_opclass","Type":"table","Metadata":{"rows":"177","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_opclass","FullyQualifiedName":"localhost/postgres/pg_opclass","Type":"table","Metadata":{"rows":"177","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_operator","FullyQualifiedName":"localhost/postgres/pg_operator","Type":"table","Metadata":{"rows":"799","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_operator","FullyQualifiedName":"localhost/postgres/pg_operator","Type":"table","Metadata":{"rows":"799","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_operator","FullyQualifiedName":"localhost/postgres/pg_operator","Type":"table","Metadata":{"rows":"799","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_operator","FullyQualifiedName":"localhost/postgres/pg_operator","Type":"table","Metadata":{"rows":"799","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_operator","FullyQualifiedName":"localhost/postgres/pg_operator","Type":"table","Metadata":{"rows":"799","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_operator","FullyQualifiedName":"localhost/postgres/pg_operator","Type":"table","Metadata":{"rows":"799","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_operator","FullyQualifiedName":"localhost/postgres/pg_operator","Type":"table","Metadata":{"rows":"799","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_opfamily","FullyQualifiedName":"localhost/postgres/pg_opfamily","Type":"table","Metadata":{"rows":"146","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_opfamily","FullyQualifiedName":"localhost/postgres/pg_opfamily","Type":"table","Metadata":{"rows":"146","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_opfamily","FullyQualifiedName":"localhost/postgres/pg_opfamily","Type":"table","Metadata":{"rows":"146","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_opfamily","FullyQualifiedName":"localhost/postgres/pg_opfamily","Type":"table","Metadata":{"rows":"146","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_opfamily","FullyQualifiedName":"localhost/postgres/pg_opfamily","Type":"table","Metadata":{"rows":"146","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_opfamily","FullyQualifiedName":"localhost/postgres/pg_opfamily","Type":"table","Metadata":{"rows":"146","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_opfamily","FullyQualifiedName":"localhost/postgres/pg_opfamily","Type":"table","Metadata":{"rows":"146","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_parameter_acl","FullyQualifiedName":"localhost/postgres/pg_parameter_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_parameter_acl","FullyQualifiedName":"localhost/postgres/pg_parameter_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_parameter_acl","FullyQualifiedName":"localhost/postgres/pg_parameter_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_parameter_acl","FullyQualifiedName":"localhost/postgres/pg_parameter_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_parameter_acl","FullyQualifiedName":"localhost/postgres/pg_parameter_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_parameter_acl","FullyQualifiedName":"localhost/postgres/pg_parameter_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_parameter_acl","FullyQualifiedName":"localhost/postgres/pg_parameter_acl","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_partitioned_table","FullyQualifiedName":"localhost/postgres/pg_partitioned_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_partitioned_table","FullyQualifiedName":"localhost/postgres/pg_partitioned_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_partitioned_table","FullyQualifiedName":"localhost/postgres/pg_partitioned_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_partitioned_table","FullyQualifiedName":"localhost/postgres/pg_partitioned_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_partitioned_table","FullyQualifiedName":"localhost/postgres/pg_partitioned_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_partitioned_table","FullyQualifiedName":"localhost/postgres/pg_partitioned_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_partitioned_table","FullyQualifiedName":"localhost/postgres/pg_partitioned_table","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_policies","FullyQualifiedName":"localhost/postgres/pg_policies","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_policies","FullyQualifiedName":"localhost/postgres/pg_policies","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_policies","FullyQualifiedName":"localhost/postgres/pg_policies","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_policies","FullyQualifiedName":"localhost/postgres/pg_policies","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_policies","FullyQualifiedName":"localhost/postgres/pg_policies","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_policies","FullyQualifiedName":"localhost/postgres/pg_policies","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_policies","FullyQualifiedName":"localhost/postgres/pg_policies","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_policy","FullyQualifiedName":"localhost/postgres/pg_policy","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_policy","FullyQualifiedName":"localhost/postgres/pg_policy","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_policy","FullyQualifiedName":"localhost/postgres/pg_policy","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_policy","FullyQualifiedName":"localhost/postgres/pg_policy","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_policy","FullyQualifiedName":"localhost/postgres/pg_policy","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_policy","FullyQualifiedName":"localhost/postgres/pg_policy","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_policy","FullyQualifiedName":"localhost/postgres/pg_policy","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_prepared_statements","FullyQualifiedName":"localhost/postgres/pg_prepared_statements","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_prepared_statements","FullyQualifiedName":"localhost/postgres/pg_prepared_statements","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_prepared_statements","FullyQualifiedName":"localhost/postgres/pg_prepared_statements","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_prepared_statements","FullyQualifiedName":"localhost/postgres/pg_prepared_statements","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_prepared_statements","FullyQualifiedName":"localhost/postgres/pg_prepared_statements","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_prepared_statements","FullyQualifiedName":"localhost/postgres/pg_prepared_statements","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_prepared_statements","FullyQualifiedName":"localhost/postgres/pg_prepared_statements","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_prepared_xacts","FullyQualifiedName":"localhost/postgres/pg_prepared_xacts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_prepared_xacts","FullyQualifiedName":"localhost/postgres/pg_prepared_xacts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_prepared_xacts","FullyQualifiedName":"localhost/postgres/pg_prepared_xacts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_prepared_xacts","FullyQualifiedName":"localhost/postgres/pg_prepared_xacts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_prepared_xacts","FullyQualifiedName":"localhost/postgres/pg_prepared_xacts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_prepared_xacts","FullyQualifiedName":"localhost/postgres/pg_prepared_xacts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_prepared_xacts","FullyQualifiedName":"localhost/postgres/pg_prepared_xacts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_proc","FullyQualifiedName":"localhost/postgres/pg_proc","Type":"table","Metadata":{"rows":"3297","size":"1216 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_proc","FullyQualifiedName":"localhost/postgres/pg_proc","Type":"table","Metadata":{"rows":"3297","size":"1216 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_proc","FullyQualifiedName":"localhost/postgres/pg_proc","Type":"table","Metadata":{"rows":"3297","size":"1216 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_proc","FullyQualifiedName":"localhost/postgres/pg_proc","Type":"table","Metadata":{"rows":"3297","size":"1216 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_proc","FullyQualifiedName":"localhost/postgres/pg_proc","Type":"table","Metadata":{"rows":"3297","size":"1216 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_proc","FullyQualifiedName":"localhost/postgres/pg_proc","Type":"table","Metadata":{"rows":"3297","size":"1216 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_proc","FullyQualifiedName":"localhost/postgres/pg_proc","Type":"table","Metadata":{"rows":"3297","size":"1216 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_publication","FullyQualifiedName":"localhost/postgres/pg_publication","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_publication","FullyQualifiedName":"localhost/postgres/pg_publication","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_publication","FullyQualifiedName":"localhost/postgres/pg_publication","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_publication","FullyQualifiedName":"localhost/postgres/pg_publication","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_publication","FullyQualifiedName":"localhost/postgres/pg_publication","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_publication","FullyQualifiedName":"localhost/postgres/pg_publication","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_publication","FullyQualifiedName":"localhost/postgres/pg_publication","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_publication_namespace","FullyQualifiedName":"localhost/postgres/pg_publication_namespace","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_publication_namespace","FullyQualifiedName":"localhost/postgres/pg_publication_namespace","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_publication_namespace","FullyQualifiedName":"localhost/postgres/pg_publication_namespace","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_publication_namespace","FullyQualifiedName":"localhost/postgres/pg_publication_namespace","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_publication_namespace","FullyQualifiedName":"localhost/postgres/pg_publication_namespace","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_publication_namespace","FullyQualifiedName":"localhost/postgres/pg_publication_namespace","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_publication_namespace","FullyQualifiedName":"localhost/postgres/pg_publication_namespace","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_publication_rel","FullyQualifiedName":"localhost/postgres/pg_publication_rel","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_publication_rel","FullyQualifiedName":"localhost/postgres/pg_publication_rel","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_publication_rel","FullyQualifiedName":"localhost/postgres/pg_publication_rel","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_publication_rel","FullyQualifiedName":"localhost/postgres/pg_publication_rel","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_publication_rel","FullyQualifiedName":"localhost/postgres/pg_publication_rel","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_publication_rel","FullyQualifiedName":"localhost/postgres/pg_publication_rel","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_publication_rel","FullyQualifiedName":"localhost/postgres/pg_publication_rel","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_publication_tables","FullyQualifiedName":"localhost/postgres/pg_publication_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_publication_tables","FullyQualifiedName":"localhost/postgres/pg_publication_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_publication_tables","FullyQualifiedName":"localhost/postgres/pg_publication_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_publication_tables","FullyQualifiedName":"localhost/postgres/pg_publication_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_publication_tables","FullyQualifiedName":"localhost/postgres/pg_publication_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_publication_tables","FullyQualifiedName":"localhost/postgres/pg_publication_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_publication_tables","FullyQualifiedName":"localhost/postgres/pg_publication_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_range","FullyQualifiedName":"localhost/postgres/pg_range","Type":"table","Metadata":{"rows":"6","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_range","FullyQualifiedName":"localhost/postgres/pg_range","Type":"table","Metadata":{"rows":"6","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_range","FullyQualifiedName":"localhost/postgres/pg_range","Type":"table","Metadata":{"rows":"6","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_range","FullyQualifiedName":"localhost/postgres/pg_range","Type":"table","Metadata":{"rows":"6","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_range","FullyQualifiedName":"localhost/postgres/pg_range","Type":"table","Metadata":{"rows":"6","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_range","FullyQualifiedName":"localhost/postgres/pg_range","Type":"table","Metadata":{"rows":"6","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_range","FullyQualifiedName":"localhost/postgres/pg_range","Type":"table","Metadata":{"rows":"6","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_replication_origin","FullyQualifiedName":"localhost/postgres/pg_replication_origin","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_replication_origin","FullyQualifiedName":"localhost/postgres/pg_replication_origin","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_replication_origin","FullyQualifiedName":"localhost/postgres/pg_replication_origin","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_replication_origin","FullyQualifiedName":"localhost/postgres/pg_replication_origin","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_replication_origin","FullyQualifiedName":"localhost/postgres/pg_replication_origin","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_replication_origin","FullyQualifiedName":"localhost/postgres/pg_replication_origin","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_replication_origin","FullyQualifiedName":"localhost/postgres/pg_replication_origin","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_replication_origin_status","FullyQualifiedName":"localhost/postgres/pg_replication_origin_status","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_replication_origin_status","FullyQualifiedName":"localhost/postgres/pg_replication_origin_status","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_replication_origin_status","FullyQualifiedName":"localhost/postgres/pg_replication_origin_status","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_replication_origin_status","FullyQualifiedName":"localhost/postgres/pg_replication_origin_status","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_replication_origin_status","FullyQualifiedName":"localhost/postgres/pg_replication_origin_status","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_replication_origin_status","FullyQualifiedName":"localhost/postgres/pg_replication_origin_status","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_replication_origin_status","FullyQualifiedName":"localhost/postgres/pg_replication_origin_status","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_replication_slots","FullyQualifiedName":"localhost/postgres/pg_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_replication_slots","FullyQualifiedName":"localhost/postgres/pg_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_replication_slots","FullyQualifiedName":"localhost/postgres/pg_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_replication_slots","FullyQualifiedName":"localhost/postgres/pg_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_replication_slots","FullyQualifiedName":"localhost/postgres/pg_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_replication_slots","FullyQualifiedName":"localhost/postgres/pg_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_replication_slots","FullyQualifiedName":"localhost/postgres/pg_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_rewrite","FullyQualifiedName":"localhost/postgres/pg_rewrite","Type":"table","Metadata":{"rows":"143","size":"728 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_rewrite","FullyQualifiedName":"localhost/postgres/pg_rewrite","Type":"table","Metadata":{"rows":"143","size":"728 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_rewrite","FullyQualifiedName":"localhost/postgres/pg_rewrite","Type":"table","Metadata":{"rows":"143","size":"728 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_rewrite","FullyQualifiedName":"localhost/postgres/pg_rewrite","Type":"table","Metadata":{"rows":"143","size":"728 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_rewrite","FullyQualifiedName":"localhost/postgres/pg_rewrite","Type":"table","Metadata":{"rows":"143","size":"728 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_rewrite","FullyQualifiedName":"localhost/postgres/pg_rewrite","Type":"table","Metadata":{"rows":"143","size":"728 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_rewrite","FullyQualifiedName":"localhost/postgres/pg_rewrite","Type":"table","Metadata":{"rows":"143","size":"728 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_roles","FullyQualifiedName":"localhost/postgres/pg_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_roles","FullyQualifiedName":"localhost/postgres/pg_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_roles","FullyQualifiedName":"localhost/postgres/pg_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_roles","FullyQualifiedName":"localhost/postgres/pg_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_roles","FullyQualifiedName":"localhost/postgres/pg_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_roles","FullyQualifiedName":"localhost/postgres/pg_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_roles","FullyQualifiedName":"localhost/postgres/pg_roles","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_rules","FullyQualifiedName":"localhost/postgres/pg_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_rules","FullyQualifiedName":"localhost/postgres/pg_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_rules","FullyQualifiedName":"localhost/postgres/pg_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_rules","FullyQualifiedName":"localhost/postgres/pg_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_rules","FullyQualifiedName":"localhost/postgres/pg_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_rules","FullyQualifiedName":"localhost/postgres/pg_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_rules","FullyQualifiedName":"localhost/postgres/pg_rules","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_seclabel","FullyQualifiedName":"localhost/postgres/pg_seclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_seclabel","FullyQualifiedName":"localhost/postgres/pg_seclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_seclabel","FullyQualifiedName":"localhost/postgres/pg_seclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_seclabel","FullyQualifiedName":"localhost/postgres/pg_seclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_seclabel","FullyQualifiedName":"localhost/postgres/pg_seclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_seclabel","FullyQualifiedName":"localhost/postgres/pg_seclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_seclabel","FullyQualifiedName":"localhost/postgres/pg_seclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_seclabels","FullyQualifiedName":"localhost/postgres/pg_seclabels","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_seclabels","FullyQualifiedName":"localhost/postgres/pg_seclabels","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_seclabels","FullyQualifiedName":"localhost/postgres/pg_seclabels","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_seclabels","FullyQualifiedName":"localhost/postgres/pg_seclabels","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_seclabels","FullyQualifiedName":"localhost/postgres/pg_seclabels","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_seclabels","FullyQualifiedName":"localhost/postgres/pg_seclabels","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_seclabels","FullyQualifiedName":"localhost/postgres/pg_seclabels","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_sequence","FullyQualifiedName":"localhost/postgres/pg_sequence","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_sequence","FullyQualifiedName":"localhost/postgres/pg_sequence","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_sequence","FullyQualifiedName":"localhost/postgres/pg_sequence","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_sequence","FullyQualifiedName":"localhost/postgres/pg_sequence","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_sequence","FullyQualifiedName":"localhost/postgres/pg_sequence","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_sequence","FullyQualifiedName":"localhost/postgres/pg_sequence","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_sequence","FullyQualifiedName":"localhost/postgres/pg_sequence","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_sequences","FullyQualifiedName":"localhost/postgres/pg_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_sequences","FullyQualifiedName":"localhost/postgres/pg_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_sequences","FullyQualifiedName":"localhost/postgres/pg_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_sequences","FullyQualifiedName":"localhost/postgres/pg_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_sequences","FullyQualifiedName":"localhost/postgres/pg_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_sequences","FullyQualifiedName":"localhost/postgres/pg_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_sequences","FullyQualifiedName":"localhost/postgres/pg_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_settings","FullyQualifiedName":"localhost/postgres/pg_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_settings","FullyQualifiedName":"localhost/postgres/pg_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_settings","FullyQualifiedName":"localhost/postgres/pg_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_settings","FullyQualifiedName":"localhost/postgres/pg_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_settings","FullyQualifiedName":"localhost/postgres/pg_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_settings","FullyQualifiedName":"localhost/postgres/pg_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_settings","FullyQualifiedName":"localhost/postgres/pg_settings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_shadow","FullyQualifiedName":"localhost/postgres/pg_shadow","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_shadow","FullyQualifiedName":"localhost/postgres/pg_shadow","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_shadow","FullyQualifiedName":"localhost/postgres/pg_shadow","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_shadow","FullyQualifiedName":"localhost/postgres/pg_shadow","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_shadow","FullyQualifiedName":"localhost/postgres/pg_shadow","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_shadow","FullyQualifiedName":"localhost/postgres/pg_shadow","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_shadow","FullyQualifiedName":"localhost/postgres/pg_shadow","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_shdepend","FullyQualifiedName":"localhost/postgres/pg_shdepend","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_shdepend","FullyQualifiedName":"localhost/postgres/pg_shdepend","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_shdepend","FullyQualifiedName":"localhost/postgres/pg_shdepend","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_shdepend","FullyQualifiedName":"localhost/postgres/pg_shdepend","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_shdepend","FullyQualifiedName":"localhost/postgres/pg_shdepend","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_shdepend","FullyQualifiedName":"localhost/postgres/pg_shdepend","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_shdepend","FullyQualifiedName":"localhost/postgres/pg_shdepend","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_shdescription","FullyQualifiedName":"localhost/postgres/pg_shdescription","Type":"table","Metadata":{"rows":"1","size":"64 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_shdescription","FullyQualifiedName":"localhost/postgres/pg_shdescription","Type":"table","Metadata":{"rows":"1","size":"64 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_shdescription","FullyQualifiedName":"localhost/postgres/pg_shdescription","Type":"table","Metadata":{"rows":"1","size":"64 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_shdescription","FullyQualifiedName":"localhost/postgres/pg_shdescription","Type":"table","Metadata":{"rows":"1","size":"64 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_shdescription","FullyQualifiedName":"localhost/postgres/pg_shdescription","Type":"table","Metadata":{"rows":"1","size":"64 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_shdescription","FullyQualifiedName":"localhost/postgres/pg_shdescription","Type":"table","Metadata":{"rows":"1","size":"64 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_shdescription","FullyQualifiedName":"localhost/postgres/pg_shdescription","Type":"table","Metadata":{"rows":"1","size":"64 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_shmem_allocations","FullyQualifiedName":"localhost/postgres/pg_shmem_allocations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_shmem_allocations","FullyQualifiedName":"localhost/postgres/pg_shmem_allocations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_shmem_allocations","FullyQualifiedName":"localhost/postgres/pg_shmem_allocations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_shmem_allocations","FullyQualifiedName":"localhost/postgres/pg_shmem_allocations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_shmem_allocations","FullyQualifiedName":"localhost/postgres/pg_shmem_allocations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_shmem_allocations","FullyQualifiedName":"localhost/postgres/pg_shmem_allocations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_shmem_allocations","FullyQualifiedName":"localhost/postgres/pg_shmem_allocations","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_shseclabel","FullyQualifiedName":"localhost/postgres/pg_shseclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_shseclabel","FullyQualifiedName":"localhost/postgres/pg_shseclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_shseclabel","FullyQualifiedName":"localhost/postgres/pg_shseclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_shseclabel","FullyQualifiedName":"localhost/postgres/pg_shseclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_shseclabel","FullyQualifiedName":"localhost/postgres/pg_shseclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_shseclabel","FullyQualifiedName":"localhost/postgres/pg_shseclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_shseclabel","FullyQualifiedName":"localhost/postgres/pg_shseclabel","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_activity","FullyQualifiedName":"localhost/postgres/pg_stat_activity","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_activity","FullyQualifiedName":"localhost/postgres/pg_stat_activity","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_activity","FullyQualifiedName":"localhost/postgres/pg_stat_activity","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_activity","FullyQualifiedName":"localhost/postgres/pg_stat_activity","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_activity","FullyQualifiedName":"localhost/postgres/pg_stat_activity","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_activity","FullyQualifiedName":"localhost/postgres/pg_stat_activity","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_activity","FullyQualifiedName":"localhost/postgres/pg_stat_activity","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_all_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_all_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_all_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_all_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_all_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_all_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_all_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_archiver","FullyQualifiedName":"localhost/postgres/pg_stat_archiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_archiver","FullyQualifiedName":"localhost/postgres/pg_stat_archiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_archiver","FullyQualifiedName":"localhost/postgres/pg_stat_archiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_archiver","FullyQualifiedName":"localhost/postgres/pg_stat_archiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_archiver","FullyQualifiedName":"localhost/postgres/pg_stat_archiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_archiver","FullyQualifiedName":"localhost/postgres/pg_stat_archiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_archiver","FullyQualifiedName":"localhost/postgres/pg_stat_archiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_bgwriter","FullyQualifiedName":"localhost/postgres/pg_stat_bgwriter","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_bgwriter","FullyQualifiedName":"localhost/postgres/pg_stat_bgwriter","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_bgwriter","FullyQualifiedName":"localhost/postgres/pg_stat_bgwriter","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_bgwriter","FullyQualifiedName":"localhost/postgres/pg_stat_bgwriter","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_bgwriter","FullyQualifiedName":"localhost/postgres/pg_stat_bgwriter","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_bgwriter","FullyQualifiedName":"localhost/postgres/pg_stat_bgwriter","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_bgwriter","FullyQualifiedName":"localhost/postgres/pg_stat_bgwriter","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_database","FullyQualifiedName":"localhost/postgres/pg_stat_database","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_database","FullyQualifiedName":"localhost/postgres/pg_stat_database","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_database","FullyQualifiedName":"localhost/postgres/pg_stat_database","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_database","FullyQualifiedName":"localhost/postgres/pg_stat_database","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_database","FullyQualifiedName":"localhost/postgres/pg_stat_database","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_database","FullyQualifiedName":"localhost/postgres/pg_stat_database","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_database","FullyQualifiedName":"localhost/postgres/pg_stat_database","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_database_conflicts","FullyQualifiedName":"localhost/postgres/pg_stat_database_conflicts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_database_conflicts","FullyQualifiedName":"localhost/postgres/pg_stat_database_conflicts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_database_conflicts","FullyQualifiedName":"localhost/postgres/pg_stat_database_conflicts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_database_conflicts","FullyQualifiedName":"localhost/postgres/pg_stat_database_conflicts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_database_conflicts","FullyQualifiedName":"localhost/postgres/pg_stat_database_conflicts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_database_conflicts","FullyQualifiedName":"localhost/postgres/pg_stat_database_conflicts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_database_conflicts","FullyQualifiedName":"localhost/postgres/pg_stat_database_conflicts","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_gssapi","FullyQualifiedName":"localhost/postgres/pg_stat_gssapi","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_gssapi","FullyQualifiedName":"localhost/postgres/pg_stat_gssapi","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_gssapi","FullyQualifiedName":"localhost/postgres/pg_stat_gssapi","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_gssapi","FullyQualifiedName":"localhost/postgres/pg_stat_gssapi","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_gssapi","FullyQualifiedName":"localhost/postgres/pg_stat_gssapi","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_gssapi","FullyQualifiedName":"localhost/postgres/pg_stat_gssapi","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_gssapi","FullyQualifiedName":"localhost/postgres/pg_stat_gssapi","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_io","FullyQualifiedName":"localhost/postgres/pg_stat_io","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_io","FullyQualifiedName":"localhost/postgres/pg_stat_io","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_io","FullyQualifiedName":"localhost/postgres/pg_stat_io","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_io","FullyQualifiedName":"localhost/postgres/pg_stat_io","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_io","FullyQualifiedName":"localhost/postgres/pg_stat_io","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_io","FullyQualifiedName":"localhost/postgres/pg_stat_io","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_io","FullyQualifiedName":"localhost/postgres/pg_stat_io","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_progress_analyze","FullyQualifiedName":"localhost/postgres/pg_stat_progress_analyze","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_progress_analyze","FullyQualifiedName":"localhost/postgres/pg_stat_progress_analyze","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_progress_analyze","FullyQualifiedName":"localhost/postgres/pg_stat_progress_analyze","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_progress_analyze","FullyQualifiedName":"localhost/postgres/pg_stat_progress_analyze","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_progress_analyze","FullyQualifiedName":"localhost/postgres/pg_stat_progress_analyze","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_progress_analyze","FullyQualifiedName":"localhost/postgres/pg_stat_progress_analyze","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_progress_analyze","FullyQualifiedName":"localhost/postgres/pg_stat_progress_analyze","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_progress_basebackup","FullyQualifiedName":"localhost/postgres/pg_stat_progress_basebackup","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_progress_basebackup","FullyQualifiedName":"localhost/postgres/pg_stat_progress_basebackup","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_progress_basebackup","FullyQualifiedName":"localhost/postgres/pg_stat_progress_basebackup","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_progress_basebackup","FullyQualifiedName":"localhost/postgres/pg_stat_progress_basebackup","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_progress_basebackup","FullyQualifiedName":"localhost/postgres/pg_stat_progress_basebackup","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_progress_basebackup","FullyQualifiedName":"localhost/postgres/pg_stat_progress_basebackup","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_progress_basebackup","FullyQualifiedName":"localhost/postgres/pg_stat_progress_basebackup","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_progress_cluster","FullyQualifiedName":"localhost/postgres/pg_stat_progress_cluster","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_progress_cluster","FullyQualifiedName":"localhost/postgres/pg_stat_progress_cluster","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_progress_cluster","FullyQualifiedName":"localhost/postgres/pg_stat_progress_cluster","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_progress_cluster","FullyQualifiedName":"localhost/postgres/pg_stat_progress_cluster","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_progress_cluster","FullyQualifiedName":"localhost/postgres/pg_stat_progress_cluster","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_progress_cluster","FullyQualifiedName":"localhost/postgres/pg_stat_progress_cluster","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_progress_cluster","FullyQualifiedName":"localhost/postgres/pg_stat_progress_cluster","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_progress_copy","FullyQualifiedName":"localhost/postgres/pg_stat_progress_copy","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_progress_copy","FullyQualifiedName":"localhost/postgres/pg_stat_progress_copy","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_progress_copy","FullyQualifiedName":"localhost/postgres/pg_stat_progress_copy","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_progress_copy","FullyQualifiedName":"localhost/postgres/pg_stat_progress_copy","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_progress_copy","FullyQualifiedName":"localhost/postgres/pg_stat_progress_copy","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_progress_copy","FullyQualifiedName":"localhost/postgres/pg_stat_progress_copy","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_progress_copy","FullyQualifiedName":"localhost/postgres/pg_stat_progress_copy","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_progress_create_index","FullyQualifiedName":"localhost/postgres/pg_stat_progress_create_index","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_progress_create_index","FullyQualifiedName":"localhost/postgres/pg_stat_progress_create_index","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_progress_create_index","FullyQualifiedName":"localhost/postgres/pg_stat_progress_create_index","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_progress_create_index","FullyQualifiedName":"localhost/postgres/pg_stat_progress_create_index","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_progress_create_index","FullyQualifiedName":"localhost/postgres/pg_stat_progress_create_index","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_progress_create_index","FullyQualifiedName":"localhost/postgres/pg_stat_progress_create_index","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_progress_create_index","FullyQualifiedName":"localhost/postgres/pg_stat_progress_create_index","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_progress_vacuum","FullyQualifiedName":"localhost/postgres/pg_stat_progress_vacuum","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_progress_vacuum","FullyQualifiedName":"localhost/postgres/pg_stat_progress_vacuum","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_progress_vacuum","FullyQualifiedName":"localhost/postgres/pg_stat_progress_vacuum","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_progress_vacuum","FullyQualifiedName":"localhost/postgres/pg_stat_progress_vacuum","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_progress_vacuum","FullyQualifiedName":"localhost/postgres/pg_stat_progress_vacuum","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_progress_vacuum","FullyQualifiedName":"localhost/postgres/pg_stat_progress_vacuum","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_progress_vacuum","FullyQualifiedName":"localhost/postgres/pg_stat_progress_vacuum","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_recovery_prefetch","FullyQualifiedName":"localhost/postgres/pg_stat_recovery_prefetch","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_recovery_prefetch","FullyQualifiedName":"localhost/postgres/pg_stat_recovery_prefetch","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_recovery_prefetch","FullyQualifiedName":"localhost/postgres/pg_stat_recovery_prefetch","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_recovery_prefetch","FullyQualifiedName":"localhost/postgres/pg_stat_recovery_prefetch","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_recovery_prefetch","FullyQualifiedName":"localhost/postgres/pg_stat_recovery_prefetch","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_recovery_prefetch","FullyQualifiedName":"localhost/postgres/pg_stat_recovery_prefetch","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_recovery_prefetch","FullyQualifiedName":"localhost/postgres/pg_stat_recovery_prefetch","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_replication","FullyQualifiedName":"localhost/postgres/pg_stat_replication","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_replication","FullyQualifiedName":"localhost/postgres/pg_stat_replication","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_replication","FullyQualifiedName":"localhost/postgres/pg_stat_replication","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_replication","FullyQualifiedName":"localhost/postgres/pg_stat_replication","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_replication","FullyQualifiedName":"localhost/postgres/pg_stat_replication","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_replication","FullyQualifiedName":"localhost/postgres/pg_stat_replication","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_replication","FullyQualifiedName":"localhost/postgres/pg_stat_replication","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_replication_slots","FullyQualifiedName":"localhost/postgres/pg_stat_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_replication_slots","FullyQualifiedName":"localhost/postgres/pg_stat_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_replication_slots","FullyQualifiedName":"localhost/postgres/pg_stat_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_replication_slots","FullyQualifiedName":"localhost/postgres/pg_stat_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_replication_slots","FullyQualifiedName":"localhost/postgres/pg_stat_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_replication_slots","FullyQualifiedName":"localhost/postgres/pg_stat_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_replication_slots","FullyQualifiedName":"localhost/postgres/pg_stat_replication_slots","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_slru","FullyQualifiedName":"localhost/postgres/pg_stat_slru","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_slru","FullyQualifiedName":"localhost/postgres/pg_stat_slru","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_slru","FullyQualifiedName":"localhost/postgres/pg_stat_slru","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_slru","FullyQualifiedName":"localhost/postgres/pg_stat_slru","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_slru","FullyQualifiedName":"localhost/postgres/pg_stat_slru","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_slru","FullyQualifiedName":"localhost/postgres/pg_stat_slru","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_slru","FullyQualifiedName":"localhost/postgres/pg_stat_slru","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_ssl","FullyQualifiedName":"localhost/postgres/pg_stat_ssl","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_ssl","FullyQualifiedName":"localhost/postgres/pg_stat_ssl","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_ssl","FullyQualifiedName":"localhost/postgres/pg_stat_ssl","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_ssl","FullyQualifiedName":"localhost/postgres/pg_stat_ssl","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_ssl","FullyQualifiedName":"localhost/postgres/pg_stat_ssl","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_ssl","FullyQualifiedName":"localhost/postgres/pg_stat_ssl","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_ssl","FullyQualifiedName":"localhost/postgres/pg_stat_ssl","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_subscription","FullyQualifiedName":"localhost/postgres/pg_stat_subscription","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_subscription","FullyQualifiedName":"localhost/postgres/pg_stat_subscription","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_subscription","FullyQualifiedName":"localhost/postgres/pg_stat_subscription","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_subscription","FullyQualifiedName":"localhost/postgres/pg_stat_subscription","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_subscription","FullyQualifiedName":"localhost/postgres/pg_stat_subscription","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_subscription","FullyQualifiedName":"localhost/postgres/pg_stat_subscription","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_subscription","FullyQualifiedName":"localhost/postgres/pg_stat_subscription","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_subscription_stats","FullyQualifiedName":"localhost/postgres/pg_stat_subscription_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_subscription_stats","FullyQualifiedName":"localhost/postgres/pg_stat_subscription_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_subscription_stats","FullyQualifiedName":"localhost/postgres/pg_stat_subscription_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_subscription_stats","FullyQualifiedName":"localhost/postgres/pg_stat_subscription_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_subscription_stats","FullyQualifiedName":"localhost/postgres/pg_stat_subscription_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_subscription_stats","FullyQualifiedName":"localhost/postgres/pg_stat_subscription_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_subscription_stats","FullyQualifiedName":"localhost/postgres/pg_stat_subscription_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_user_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_user_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_user_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_user_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_user_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_user_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_user_indexes","FullyQualifiedName":"localhost/postgres/pg_stat_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_wal","FullyQualifiedName":"localhost/postgres/pg_stat_wal","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_wal","FullyQualifiedName":"localhost/postgres/pg_stat_wal","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_wal","FullyQualifiedName":"localhost/postgres/pg_stat_wal","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_wal","FullyQualifiedName":"localhost/postgres/pg_stat_wal","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_wal","FullyQualifiedName":"localhost/postgres/pg_stat_wal","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_wal","FullyQualifiedName":"localhost/postgres/pg_stat_wal","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_wal","FullyQualifiedName":"localhost/postgres/pg_stat_wal","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_wal_receiver","FullyQualifiedName":"localhost/postgres/pg_stat_wal_receiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_wal_receiver","FullyQualifiedName":"localhost/postgres/pg_stat_wal_receiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_wal_receiver","FullyQualifiedName":"localhost/postgres/pg_stat_wal_receiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_wal_receiver","FullyQualifiedName":"localhost/postgres/pg_stat_wal_receiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_wal_receiver","FullyQualifiedName":"localhost/postgres/pg_stat_wal_receiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_wal_receiver","FullyQualifiedName":"localhost/postgres/pg_stat_wal_receiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_wal_receiver","FullyQualifiedName":"localhost/postgres/pg_stat_wal_receiver","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_xact_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_xact_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_xact_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_xact_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_xact_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_xact_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_xact_all_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_xact_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_xact_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_xact_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_xact_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_xact_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_xact_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_xact_sys_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_functions","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_functions","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stat_xact_user_tables","FullyQualifiedName":"localhost/postgres/pg_stat_xact_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statio_all_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statio_all_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statio_all_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statio_all_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statio_all_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statio_all_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statio_all_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_all_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statio_all_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_all_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statio_all_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_all_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statio_all_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_all_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statio_all_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_all_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statio_all_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_all_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statio_all_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_all_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statio_all_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_all_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statio_all_tables","FullyQualifiedName":"localhost/postgres/pg_statio_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statio_all_tables","FullyQualifiedName":"localhost/postgres/pg_statio_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statio_all_tables","FullyQualifiedName":"localhost/postgres/pg_statio_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statio_all_tables","FullyQualifiedName":"localhost/postgres/pg_statio_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statio_all_tables","FullyQualifiedName":"localhost/postgres/pg_statio_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statio_all_tables","FullyQualifiedName":"localhost/postgres/pg_statio_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statio_all_tables","FullyQualifiedName":"localhost/postgres/pg_statio_all_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statio_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statio_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statio_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statio_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statio_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statio_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statio_sys_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_sys_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statio_sys_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_sys_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statio_sys_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_sys_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statio_sys_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_sys_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statio_sys_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_sys_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statio_sys_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_sys_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statio_sys_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_sys_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statio_sys_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_sys_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statio_sys_tables","FullyQualifiedName":"localhost/postgres/pg_statio_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statio_sys_tables","FullyQualifiedName":"localhost/postgres/pg_statio_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statio_sys_tables","FullyQualifiedName":"localhost/postgres/pg_statio_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statio_sys_tables","FullyQualifiedName":"localhost/postgres/pg_statio_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statio_sys_tables","FullyQualifiedName":"localhost/postgres/pg_statio_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statio_sys_tables","FullyQualifiedName":"localhost/postgres/pg_statio_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statio_sys_tables","FullyQualifiedName":"localhost/postgres/pg_statio_sys_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statio_user_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statio_user_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statio_user_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statio_user_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statio_user_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statio_user_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statio_user_indexes","FullyQualifiedName":"localhost/postgres/pg_statio_user_indexes","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statio_user_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_user_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statio_user_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_user_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statio_user_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_user_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statio_user_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_user_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statio_user_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_user_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statio_user_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_user_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statio_user_sequences","FullyQualifiedName":"localhost/postgres/pg_statio_user_sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statio_user_tables","FullyQualifiedName":"localhost/postgres/pg_statio_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statio_user_tables","FullyQualifiedName":"localhost/postgres/pg_statio_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statio_user_tables","FullyQualifiedName":"localhost/postgres/pg_statio_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statio_user_tables","FullyQualifiedName":"localhost/postgres/pg_statio_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statio_user_tables","FullyQualifiedName":"localhost/postgres/pg_statio_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statio_user_tables","FullyQualifiedName":"localhost/postgres/pg_statio_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statio_user_tables","FullyQualifiedName":"localhost/postgres/pg_statio_user_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statistic","FullyQualifiedName":"localhost/postgres/pg_statistic","Type":"table","Metadata":{"rows":"409","size":"288 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statistic","FullyQualifiedName":"localhost/postgres/pg_statistic","Type":"table","Metadata":{"rows":"409","size":"288 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statistic","FullyQualifiedName":"localhost/postgres/pg_statistic","Type":"table","Metadata":{"rows":"409","size":"288 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statistic","FullyQualifiedName":"localhost/postgres/pg_statistic","Type":"table","Metadata":{"rows":"409","size":"288 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statistic","FullyQualifiedName":"localhost/postgres/pg_statistic","Type":"table","Metadata":{"rows":"409","size":"288 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statistic","FullyQualifiedName":"localhost/postgres/pg_statistic","Type":"table","Metadata":{"rows":"409","size":"288 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statistic","FullyQualifiedName":"localhost/postgres/pg_statistic","Type":"table","Metadata":{"rows":"409","size":"288 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statistic_ext","FullyQualifiedName":"localhost/postgres/pg_statistic_ext","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statistic_ext","FullyQualifiedName":"localhost/postgres/pg_statistic_ext","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statistic_ext","FullyQualifiedName":"localhost/postgres/pg_statistic_ext","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statistic_ext","FullyQualifiedName":"localhost/postgres/pg_statistic_ext","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statistic_ext","FullyQualifiedName":"localhost/postgres/pg_statistic_ext","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statistic_ext","FullyQualifiedName":"localhost/postgres/pg_statistic_ext","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statistic_ext","FullyQualifiedName":"localhost/postgres/pg_statistic_ext","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_statistic_ext_data","FullyQualifiedName":"localhost/postgres/pg_statistic_ext_data","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_statistic_ext_data","FullyQualifiedName":"localhost/postgres/pg_statistic_ext_data","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_statistic_ext_data","FullyQualifiedName":"localhost/postgres/pg_statistic_ext_data","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_statistic_ext_data","FullyQualifiedName":"localhost/postgres/pg_statistic_ext_data","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_statistic_ext_data","FullyQualifiedName":"localhost/postgres/pg_statistic_ext_data","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_statistic_ext_data","FullyQualifiedName":"localhost/postgres/pg_statistic_ext_data","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_statistic_ext_data","FullyQualifiedName":"localhost/postgres/pg_statistic_ext_data","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stats","FullyQualifiedName":"localhost/postgres/pg_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stats","FullyQualifiedName":"localhost/postgres/pg_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stats","FullyQualifiedName":"localhost/postgres/pg_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stats","FullyQualifiedName":"localhost/postgres/pg_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stats","FullyQualifiedName":"localhost/postgres/pg_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stats","FullyQualifiedName":"localhost/postgres/pg_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stats","FullyQualifiedName":"localhost/postgres/pg_stats","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stats_ext","FullyQualifiedName":"localhost/postgres/pg_stats_ext","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stats_ext","FullyQualifiedName":"localhost/postgres/pg_stats_ext","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stats_ext","FullyQualifiedName":"localhost/postgres/pg_stats_ext","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stats_ext","FullyQualifiedName":"localhost/postgres/pg_stats_ext","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stats_ext","FullyQualifiedName":"localhost/postgres/pg_stats_ext","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stats_ext","FullyQualifiedName":"localhost/postgres/pg_stats_ext","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stats_ext","FullyQualifiedName":"localhost/postgres/pg_stats_ext","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_stats_ext_exprs","FullyQualifiedName":"localhost/postgres/pg_stats_ext_exprs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_stats_ext_exprs","FullyQualifiedName":"localhost/postgres/pg_stats_ext_exprs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_stats_ext_exprs","FullyQualifiedName":"localhost/postgres/pg_stats_ext_exprs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_stats_ext_exprs","FullyQualifiedName":"localhost/postgres/pg_stats_ext_exprs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_stats_ext_exprs","FullyQualifiedName":"localhost/postgres/pg_stats_ext_exprs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_stats_ext_exprs","FullyQualifiedName":"localhost/postgres/pg_stats_ext_exprs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_stats_ext_exprs","FullyQualifiedName":"localhost/postgres/pg_stats_ext_exprs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_subscription","FullyQualifiedName":"localhost/postgres/pg_subscription","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_subscription","FullyQualifiedName":"localhost/postgres/pg_subscription","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_subscription","FullyQualifiedName":"localhost/postgres/pg_subscription","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_subscription","FullyQualifiedName":"localhost/postgres/pg_subscription","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_subscription","FullyQualifiedName":"localhost/postgres/pg_subscription","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_subscription","FullyQualifiedName":"localhost/postgres/pg_subscription","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_subscription","FullyQualifiedName":"localhost/postgres/pg_subscription","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_subscription_rel","FullyQualifiedName":"localhost/postgres/pg_subscription_rel","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_subscription_rel","FullyQualifiedName":"localhost/postgres/pg_subscription_rel","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_subscription_rel","FullyQualifiedName":"localhost/postgres/pg_subscription_rel","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_subscription_rel","FullyQualifiedName":"localhost/postgres/pg_subscription_rel","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_subscription_rel","FullyQualifiedName":"localhost/postgres/pg_subscription_rel","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_subscription_rel","FullyQualifiedName":"localhost/postgres/pg_subscription_rel","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_subscription_rel","FullyQualifiedName":"localhost/postgres/pg_subscription_rel","Type":"table","Metadata":{"rows":"0","size":"8192 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_tables","FullyQualifiedName":"localhost/postgres/pg_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_tables","FullyQualifiedName":"localhost/postgres/pg_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_tables","FullyQualifiedName":"localhost/postgres/pg_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_tables","FullyQualifiedName":"localhost/postgres/pg_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_tables","FullyQualifiedName":"localhost/postgres/pg_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_tables","FullyQualifiedName":"localhost/postgres/pg_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_tables","FullyQualifiedName":"localhost/postgres/pg_tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_tablespace","FullyQualifiedName":"localhost/postgres/pg_tablespace","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_tablespace","FullyQualifiedName":"localhost/postgres/pg_tablespace","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_tablespace","FullyQualifiedName":"localhost/postgres/pg_tablespace","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_tablespace","FullyQualifiedName":"localhost/postgres/pg_tablespace","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_tablespace","FullyQualifiedName":"localhost/postgres/pg_tablespace","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_tablespace","FullyQualifiedName":"localhost/postgres/pg_tablespace","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_tablespace","FullyQualifiedName":"localhost/postgres/pg_tablespace","Type":"table","Metadata":{"rows":"2","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_timezone_abbrevs","FullyQualifiedName":"localhost/postgres/pg_timezone_abbrevs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_timezone_abbrevs","FullyQualifiedName":"localhost/postgres/pg_timezone_abbrevs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_timezone_abbrevs","FullyQualifiedName":"localhost/postgres/pg_timezone_abbrevs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_timezone_abbrevs","FullyQualifiedName":"localhost/postgres/pg_timezone_abbrevs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_timezone_abbrevs","FullyQualifiedName":"localhost/postgres/pg_timezone_abbrevs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_timezone_abbrevs","FullyQualifiedName":"localhost/postgres/pg_timezone_abbrevs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_timezone_abbrevs","FullyQualifiedName":"localhost/postgres/pg_timezone_abbrevs","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_timezone_names","FullyQualifiedName":"localhost/postgres/pg_timezone_names","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_timezone_names","FullyQualifiedName":"localhost/postgres/pg_timezone_names","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_timezone_names","FullyQualifiedName":"localhost/postgres/pg_timezone_names","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_timezone_names","FullyQualifiedName":"localhost/postgres/pg_timezone_names","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_timezone_names","FullyQualifiedName":"localhost/postgres/pg_timezone_names","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_timezone_names","FullyQualifiedName":"localhost/postgres/pg_timezone_names","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_timezone_names","FullyQualifiedName":"localhost/postgres/pg_timezone_names","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_transform","FullyQualifiedName":"localhost/postgres/pg_transform","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_transform","FullyQualifiedName":"localhost/postgres/pg_transform","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_transform","FullyQualifiedName":"localhost/postgres/pg_transform","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_transform","FullyQualifiedName":"localhost/postgres/pg_transform","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_transform","FullyQualifiedName":"localhost/postgres/pg_transform","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_transform","FullyQualifiedName":"localhost/postgres/pg_transform","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_transform","FullyQualifiedName":"localhost/postgres/pg_transform","Type":"table","Metadata":{"rows":"0","size":"16 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_trigger","FullyQualifiedName":"localhost/postgres/pg_trigger","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_trigger","FullyQualifiedName":"localhost/postgres/pg_trigger","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_trigger","FullyQualifiedName":"localhost/postgres/pg_trigger","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_trigger","FullyQualifiedName":"localhost/postgres/pg_trigger","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_trigger","FullyQualifiedName":"localhost/postgres/pg_trigger","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_trigger","FullyQualifiedName":"localhost/postgres/pg_trigger","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_trigger","FullyQualifiedName":"localhost/postgres/pg_trigger","Type":"table","Metadata":{"rows":"0","size":"32 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_ts_config","FullyQualifiedName":"localhost/postgres/pg_ts_config","Type":"table","Metadata":{"rows":"29","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_ts_config","FullyQualifiedName":"localhost/postgres/pg_ts_config","Type":"table","Metadata":{"rows":"29","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_ts_config","FullyQualifiedName":"localhost/postgres/pg_ts_config","Type":"table","Metadata":{"rows":"29","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_ts_config","FullyQualifiedName":"localhost/postgres/pg_ts_config","Type":"table","Metadata":{"rows":"29","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_ts_config","FullyQualifiedName":"localhost/postgres/pg_ts_config","Type":"table","Metadata":{"rows":"29","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_ts_config","FullyQualifiedName":"localhost/postgres/pg_ts_config","Type":"table","Metadata":{"rows":"29","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_ts_config","FullyQualifiedName":"localhost/postgres/pg_ts_config","Type":"table","Metadata":{"rows":"29","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_ts_config_map","FullyQualifiedName":"localhost/postgres/pg_ts_config_map","Type":"table","Metadata":{"rows":"551","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_ts_config_map","FullyQualifiedName":"localhost/postgres/pg_ts_config_map","Type":"table","Metadata":{"rows":"551","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_ts_config_map","FullyQualifiedName":"localhost/postgres/pg_ts_config_map","Type":"table","Metadata":{"rows":"551","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_ts_config_map","FullyQualifiedName":"localhost/postgres/pg_ts_config_map","Type":"table","Metadata":{"rows":"551","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_ts_config_map","FullyQualifiedName":"localhost/postgres/pg_ts_config_map","Type":"table","Metadata":{"rows":"551","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_ts_config_map","FullyQualifiedName":"localhost/postgres/pg_ts_config_map","Type":"table","Metadata":{"rows":"551","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_ts_config_map","FullyQualifiedName":"localhost/postgres/pg_ts_config_map","Type":"table","Metadata":{"rows":"551","size":"88 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_ts_dict","FullyQualifiedName":"localhost/postgres/pg_ts_dict","Type":"table","Metadata":{"rows":"29","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_ts_dict","FullyQualifiedName":"localhost/postgres/pg_ts_dict","Type":"table","Metadata":{"rows":"29","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_ts_dict","FullyQualifiedName":"localhost/postgres/pg_ts_dict","Type":"table","Metadata":{"rows":"29","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_ts_dict","FullyQualifiedName":"localhost/postgres/pg_ts_dict","Type":"table","Metadata":{"rows":"29","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_ts_dict","FullyQualifiedName":"localhost/postgres/pg_ts_dict","Type":"table","Metadata":{"rows":"29","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_ts_dict","FullyQualifiedName":"localhost/postgres/pg_ts_dict","Type":"table","Metadata":{"rows":"29","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_ts_dict","FullyQualifiedName":"localhost/postgres/pg_ts_dict","Type":"table","Metadata":{"rows":"29","size":"80 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_ts_parser","FullyQualifiedName":"localhost/postgres/pg_ts_parser","Type":"table","Metadata":{"rows":"1","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_ts_parser","FullyQualifiedName":"localhost/postgres/pg_ts_parser","Type":"table","Metadata":{"rows":"1","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_ts_parser","FullyQualifiedName":"localhost/postgres/pg_ts_parser","Type":"table","Metadata":{"rows":"1","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_ts_parser","FullyQualifiedName":"localhost/postgres/pg_ts_parser","Type":"table","Metadata":{"rows":"1","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_ts_parser","FullyQualifiedName":"localhost/postgres/pg_ts_parser","Type":"table","Metadata":{"rows":"1","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_ts_parser","FullyQualifiedName":"localhost/postgres/pg_ts_parser","Type":"table","Metadata":{"rows":"1","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_ts_parser","FullyQualifiedName":"localhost/postgres/pg_ts_parser","Type":"table","Metadata":{"rows":"1","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_ts_template","FullyQualifiedName":"localhost/postgres/pg_ts_template","Type":"table","Metadata":{"rows":"5","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_ts_template","FullyQualifiedName":"localhost/postgres/pg_ts_template","Type":"table","Metadata":{"rows":"5","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_ts_template","FullyQualifiedName":"localhost/postgres/pg_ts_template","Type":"table","Metadata":{"rows":"5","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_ts_template","FullyQualifiedName":"localhost/postgres/pg_ts_template","Type":"table","Metadata":{"rows":"5","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_ts_template","FullyQualifiedName":"localhost/postgres/pg_ts_template","Type":"table","Metadata":{"rows":"5","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_ts_template","FullyQualifiedName":"localhost/postgres/pg_ts_template","Type":"table","Metadata":{"rows":"5","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_ts_template","FullyQualifiedName":"localhost/postgres/pg_ts_template","Type":"table","Metadata":{"rows":"5","size":"72 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_type","FullyQualifiedName":"localhost/postgres/pg_type","Type":"table","Metadata":{"rows":"613","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_type","FullyQualifiedName":"localhost/postgres/pg_type","Type":"table","Metadata":{"rows":"613","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_type","FullyQualifiedName":"localhost/postgres/pg_type","Type":"table","Metadata":{"rows":"613","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_type","FullyQualifiedName":"localhost/postgres/pg_type","Type":"table","Metadata":{"rows":"613","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_type","FullyQualifiedName":"localhost/postgres/pg_type","Type":"table","Metadata":{"rows":"613","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_type","FullyQualifiedName":"localhost/postgres/pg_type","Type":"table","Metadata":{"rows":"613","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_type","FullyQualifiedName":"localhost/postgres/pg_type","Type":"table","Metadata":{"rows":"613","size":"232 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_user","FullyQualifiedName":"localhost/postgres/pg_user","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_user","FullyQualifiedName":"localhost/postgres/pg_user","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_user","FullyQualifiedName":"localhost/postgres/pg_user","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_user","FullyQualifiedName":"localhost/postgres/pg_user","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_user","FullyQualifiedName":"localhost/postgres/pg_user","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_user","FullyQualifiedName":"localhost/postgres/pg_user","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_user","FullyQualifiedName":"localhost/postgres/pg_user","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_user_mapping","FullyQualifiedName":"localhost/postgres/pg_user_mapping","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_user_mapping","FullyQualifiedName":"localhost/postgres/pg_user_mapping","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_user_mapping","FullyQualifiedName":"localhost/postgres/pg_user_mapping","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_user_mapping","FullyQualifiedName":"localhost/postgres/pg_user_mapping","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_user_mapping","FullyQualifiedName":"localhost/postgres/pg_user_mapping","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_user_mapping","FullyQualifiedName":"localhost/postgres/pg_user_mapping","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_user_mapping","FullyQualifiedName":"localhost/postgres/pg_user_mapping","Type":"table","Metadata":{"rows":"0","size":"24 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_user_mappings","FullyQualifiedName":"localhost/postgres/pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_user_mappings","FullyQualifiedName":"localhost/postgres/pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_user_mappings","FullyQualifiedName":"localhost/postgres/pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_user_mappings","FullyQualifiedName":"localhost/postgres/pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_user_mappings","FullyQualifiedName":"localhost/postgres/pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_user_mappings","FullyQualifiedName":"localhost/postgres/pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_user_mappings","FullyQualifiedName":"localhost/postgres/pg_user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"pg_views","FullyQualifiedName":"localhost/postgres/pg_views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"pg_views","FullyQualifiedName":"localhost/postgres/pg_views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"pg_views","FullyQualifiedName":"localhost/postgres/pg_views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"pg_views","FullyQualifiedName":"localhost/postgres/pg_views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"pg_views","FullyQualifiedName":"localhost/postgres/pg_views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"pg_views","FullyQualifiedName":"localhost/postgres/pg_views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"pg_views","FullyQualifiedName":"localhost/postgres/pg_views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null},"Permission":{"Value":"Bypass RLS","Parent":null}},{"Resource":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null},"Permission":{"Value":"Create DB","Parent":null}},{"Resource":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null},"Permission":{"Value":"Create Role","Parent":null}},{"Resource":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null},"Permission":{"Value":"Inheritance of Privs","Parent":null}},{"Resource":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null},"Permission":{"Value":"Login","Parent":null}},{"Resource":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null},"Permission":{"Value":"Replication","Parent":null}},{"Resource":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null},"Permission":{"Value":"Superuser","Parent":null}},{"Resource":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}},"Permission":{"Value":"connect","Parent":null}},{"Resource":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}},"Permission":{"Value":"create","Parent":null}},{"Resource":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}},"Permission":{"Value":"temp","Parent":null}},{"Resource":{"Name":"referential_constraints","FullyQualifiedName":"localhost/postgres/referential_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"referential_constraints","FullyQualifiedName":"localhost/postgres/referential_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"referential_constraints","FullyQualifiedName":"localhost/postgres/referential_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"referential_constraints","FullyQualifiedName":"localhost/postgres/referential_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"referential_constraints","FullyQualifiedName":"localhost/postgres/referential_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"referential_constraints","FullyQualifiedName":"localhost/postgres/referential_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"referential_constraints","FullyQualifiedName":"localhost/postgres/referential_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"role_column_grants","FullyQualifiedName":"localhost/postgres/role_column_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"role_column_grants","FullyQualifiedName":"localhost/postgres/role_column_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"role_column_grants","FullyQualifiedName":"localhost/postgres/role_column_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"role_column_grants","FullyQualifiedName":"localhost/postgres/role_column_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"role_column_grants","FullyQualifiedName":"localhost/postgres/role_column_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"role_column_grants","FullyQualifiedName":"localhost/postgres/role_column_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"role_column_grants","FullyQualifiedName":"localhost/postgres/role_column_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"role_routine_grants","FullyQualifiedName":"localhost/postgres/role_routine_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"role_routine_grants","FullyQualifiedName":"localhost/postgres/role_routine_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"role_routine_grants","FullyQualifiedName":"localhost/postgres/role_routine_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"role_routine_grants","FullyQualifiedName":"localhost/postgres/role_routine_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"role_routine_grants","FullyQualifiedName":"localhost/postgres/role_routine_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"role_routine_grants","FullyQualifiedName":"localhost/postgres/role_routine_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"role_routine_grants","FullyQualifiedName":"localhost/postgres/role_routine_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"role_table_grants","FullyQualifiedName":"localhost/postgres/role_table_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"role_table_grants","FullyQualifiedName":"localhost/postgres/role_table_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"role_table_grants","FullyQualifiedName":"localhost/postgres/role_table_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"role_table_grants","FullyQualifiedName":"localhost/postgres/role_table_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"role_table_grants","FullyQualifiedName":"localhost/postgres/role_table_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"role_table_grants","FullyQualifiedName":"localhost/postgres/role_table_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"role_table_grants","FullyQualifiedName":"localhost/postgres/role_table_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"role_udt_grants","FullyQualifiedName":"localhost/postgres/role_udt_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"role_udt_grants","FullyQualifiedName":"localhost/postgres/role_udt_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"role_udt_grants","FullyQualifiedName":"localhost/postgres/role_udt_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"role_udt_grants","FullyQualifiedName":"localhost/postgres/role_udt_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"role_udt_grants","FullyQualifiedName":"localhost/postgres/role_udt_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"role_udt_grants","FullyQualifiedName":"localhost/postgres/role_udt_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"role_udt_grants","FullyQualifiedName":"localhost/postgres/role_udt_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"role_usage_grants","FullyQualifiedName":"localhost/postgres/role_usage_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"role_usage_grants","FullyQualifiedName":"localhost/postgres/role_usage_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"role_usage_grants","FullyQualifiedName":"localhost/postgres/role_usage_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"role_usage_grants","FullyQualifiedName":"localhost/postgres/role_usage_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"role_usage_grants","FullyQualifiedName":"localhost/postgres/role_usage_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"role_usage_grants","FullyQualifiedName":"localhost/postgres/role_usage_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"role_usage_grants","FullyQualifiedName":"localhost/postgres/role_usage_grants","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"routine_column_usage","FullyQualifiedName":"localhost/postgres/routine_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"routine_column_usage","FullyQualifiedName":"localhost/postgres/routine_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"routine_column_usage","FullyQualifiedName":"localhost/postgres/routine_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"routine_column_usage","FullyQualifiedName":"localhost/postgres/routine_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"routine_column_usage","FullyQualifiedName":"localhost/postgres/routine_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"routine_column_usage","FullyQualifiedName":"localhost/postgres/routine_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"routine_column_usage","FullyQualifiedName":"localhost/postgres/routine_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"routine_privileges","FullyQualifiedName":"localhost/postgres/routine_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"routine_privileges","FullyQualifiedName":"localhost/postgres/routine_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"routine_privileges","FullyQualifiedName":"localhost/postgres/routine_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"routine_privileges","FullyQualifiedName":"localhost/postgres/routine_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"routine_privileges","FullyQualifiedName":"localhost/postgres/routine_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"routine_privileges","FullyQualifiedName":"localhost/postgres/routine_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"routine_privileges","FullyQualifiedName":"localhost/postgres/routine_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"routine_routine_usage","FullyQualifiedName":"localhost/postgres/routine_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"routine_routine_usage","FullyQualifiedName":"localhost/postgres/routine_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"routine_routine_usage","FullyQualifiedName":"localhost/postgres/routine_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"routine_routine_usage","FullyQualifiedName":"localhost/postgres/routine_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"routine_routine_usage","FullyQualifiedName":"localhost/postgres/routine_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"routine_routine_usage","FullyQualifiedName":"localhost/postgres/routine_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"routine_routine_usage","FullyQualifiedName":"localhost/postgres/routine_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"routine_sequence_usage","FullyQualifiedName":"localhost/postgres/routine_sequence_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"routine_sequence_usage","FullyQualifiedName":"localhost/postgres/routine_sequence_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"routine_sequence_usage","FullyQualifiedName":"localhost/postgres/routine_sequence_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"routine_sequence_usage","FullyQualifiedName":"localhost/postgres/routine_sequence_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"routine_sequence_usage","FullyQualifiedName":"localhost/postgres/routine_sequence_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"routine_sequence_usage","FullyQualifiedName":"localhost/postgres/routine_sequence_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"routine_sequence_usage","FullyQualifiedName":"localhost/postgres/routine_sequence_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"routine_table_usage","FullyQualifiedName":"localhost/postgres/routine_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"routine_table_usage","FullyQualifiedName":"localhost/postgres/routine_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"routine_table_usage","FullyQualifiedName":"localhost/postgres/routine_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"routine_table_usage","FullyQualifiedName":"localhost/postgres/routine_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"routine_table_usage","FullyQualifiedName":"localhost/postgres/routine_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"routine_table_usage","FullyQualifiedName":"localhost/postgres/routine_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"routine_table_usage","FullyQualifiedName":"localhost/postgres/routine_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"routines","FullyQualifiedName":"localhost/postgres/routines","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"routines","FullyQualifiedName":"localhost/postgres/routines","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"routines","FullyQualifiedName":"localhost/postgres/routines","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"routines","FullyQualifiedName":"localhost/postgres/routines","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"routines","FullyQualifiedName":"localhost/postgres/routines","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"routines","FullyQualifiedName":"localhost/postgres/routines","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"routines","FullyQualifiedName":"localhost/postgres/routines","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"schemata","FullyQualifiedName":"localhost/postgres/schemata","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"schemata","FullyQualifiedName":"localhost/postgres/schemata","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"schemata","FullyQualifiedName":"localhost/postgres/schemata","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"schemata","FullyQualifiedName":"localhost/postgres/schemata","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"schemata","FullyQualifiedName":"localhost/postgres/schemata","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"schemata","FullyQualifiedName":"localhost/postgres/schemata","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"schemata","FullyQualifiedName":"localhost/postgres/schemata","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"sequences","FullyQualifiedName":"localhost/postgres/sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"sequences","FullyQualifiedName":"localhost/postgres/sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"sequences","FullyQualifiedName":"localhost/postgres/sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"sequences","FullyQualifiedName":"localhost/postgres/sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"sequences","FullyQualifiedName":"localhost/postgres/sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"sequences","FullyQualifiedName":"localhost/postgres/sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"sequences","FullyQualifiedName":"localhost/postgres/sequences","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"sql_features","FullyQualifiedName":"localhost/postgres/sql_features","Type":"table","Metadata":{"rows":"755","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"sql_features","FullyQualifiedName":"localhost/postgres/sql_features","Type":"table","Metadata":{"rows":"755","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"sql_features","FullyQualifiedName":"localhost/postgres/sql_features","Type":"table","Metadata":{"rows":"755","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"sql_features","FullyQualifiedName":"localhost/postgres/sql_features","Type":"table","Metadata":{"rows":"755","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"sql_features","FullyQualifiedName":"localhost/postgres/sql_features","Type":"table","Metadata":{"rows":"755","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"sql_features","FullyQualifiedName":"localhost/postgres/sql_features","Type":"table","Metadata":{"rows":"755","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"sql_features","FullyQualifiedName":"localhost/postgres/sql_features","Type":"table","Metadata":{"rows":"755","size":"104 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"sql_implementation_info","FullyQualifiedName":"localhost/postgres/sql_implementation_info","Type":"table","Metadata":{"rows":"12","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"sql_implementation_info","FullyQualifiedName":"localhost/postgres/sql_implementation_info","Type":"table","Metadata":{"rows":"12","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"sql_implementation_info","FullyQualifiedName":"localhost/postgres/sql_implementation_info","Type":"table","Metadata":{"rows":"12","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"sql_implementation_info","FullyQualifiedName":"localhost/postgres/sql_implementation_info","Type":"table","Metadata":{"rows":"12","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"sql_implementation_info","FullyQualifiedName":"localhost/postgres/sql_implementation_info","Type":"table","Metadata":{"rows":"12","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"sql_implementation_info","FullyQualifiedName":"localhost/postgres/sql_implementation_info","Type":"table","Metadata":{"rows":"12","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"sql_implementation_info","FullyQualifiedName":"localhost/postgres/sql_implementation_info","Type":"table","Metadata":{"rows":"12","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"sql_parts","FullyQualifiedName":"localhost/postgres/sql_parts","Type":"table","Metadata":{"rows":"11","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"sql_parts","FullyQualifiedName":"localhost/postgres/sql_parts","Type":"table","Metadata":{"rows":"11","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"sql_parts","FullyQualifiedName":"localhost/postgres/sql_parts","Type":"table","Metadata":{"rows":"11","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"sql_parts","FullyQualifiedName":"localhost/postgres/sql_parts","Type":"table","Metadata":{"rows":"11","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"sql_parts","FullyQualifiedName":"localhost/postgres/sql_parts","Type":"table","Metadata":{"rows":"11","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"sql_parts","FullyQualifiedName":"localhost/postgres/sql_parts","Type":"table","Metadata":{"rows":"11","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"sql_parts","FullyQualifiedName":"localhost/postgres/sql_parts","Type":"table","Metadata":{"rows":"11","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"sql_sizing","FullyQualifiedName":"localhost/postgres/sql_sizing","Type":"table","Metadata":{"rows":"23","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"sql_sizing","FullyQualifiedName":"localhost/postgres/sql_sizing","Type":"table","Metadata":{"rows":"23","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"sql_sizing","FullyQualifiedName":"localhost/postgres/sql_sizing","Type":"table","Metadata":{"rows":"23","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"sql_sizing","FullyQualifiedName":"localhost/postgres/sql_sizing","Type":"table","Metadata":{"rows":"23","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"sql_sizing","FullyQualifiedName":"localhost/postgres/sql_sizing","Type":"table","Metadata":{"rows":"23","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"sql_sizing","FullyQualifiedName":"localhost/postgres/sql_sizing","Type":"table","Metadata":{"rows":"23","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"sql_sizing","FullyQualifiedName":"localhost/postgres/sql_sizing","Type":"table","Metadata":{"rows":"23","size":"48 kB"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"table_constraints","FullyQualifiedName":"localhost/postgres/table_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"table_constraints","FullyQualifiedName":"localhost/postgres/table_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"table_constraints","FullyQualifiedName":"localhost/postgres/table_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"table_constraints","FullyQualifiedName":"localhost/postgres/table_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"table_constraints","FullyQualifiedName":"localhost/postgres/table_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"table_constraints","FullyQualifiedName":"localhost/postgres/table_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"table_constraints","FullyQualifiedName":"localhost/postgres/table_constraints","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"table_privileges","FullyQualifiedName":"localhost/postgres/table_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"table_privileges","FullyQualifiedName":"localhost/postgres/table_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"table_privileges","FullyQualifiedName":"localhost/postgres/table_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"table_privileges","FullyQualifiedName":"localhost/postgres/table_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"table_privileges","FullyQualifiedName":"localhost/postgres/table_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"table_privileges","FullyQualifiedName":"localhost/postgres/table_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"table_privileges","FullyQualifiedName":"localhost/postgres/table_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"tables","FullyQualifiedName":"localhost/postgres/tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"tables","FullyQualifiedName":"localhost/postgres/tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"tables","FullyQualifiedName":"localhost/postgres/tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"tables","FullyQualifiedName":"localhost/postgres/tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"tables","FullyQualifiedName":"localhost/postgres/tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"tables","FullyQualifiedName":"localhost/postgres/tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"tables","FullyQualifiedName":"localhost/postgres/tables","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"transforms","FullyQualifiedName":"localhost/postgres/transforms","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"transforms","FullyQualifiedName":"localhost/postgres/transforms","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"transforms","FullyQualifiedName":"localhost/postgres/transforms","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"transforms","FullyQualifiedName":"localhost/postgres/transforms","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"transforms","FullyQualifiedName":"localhost/postgres/transforms","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"transforms","FullyQualifiedName":"localhost/postgres/transforms","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"transforms","FullyQualifiedName":"localhost/postgres/transforms","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"triggered_update_columns","FullyQualifiedName":"localhost/postgres/triggered_update_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"triggered_update_columns","FullyQualifiedName":"localhost/postgres/triggered_update_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"triggered_update_columns","FullyQualifiedName":"localhost/postgres/triggered_update_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"triggered_update_columns","FullyQualifiedName":"localhost/postgres/triggered_update_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"triggered_update_columns","FullyQualifiedName":"localhost/postgres/triggered_update_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"triggered_update_columns","FullyQualifiedName":"localhost/postgres/triggered_update_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"triggered_update_columns","FullyQualifiedName":"localhost/postgres/triggered_update_columns","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"triggers","FullyQualifiedName":"localhost/postgres/triggers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"triggers","FullyQualifiedName":"localhost/postgres/triggers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"triggers","FullyQualifiedName":"localhost/postgres/triggers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"triggers","FullyQualifiedName":"localhost/postgres/triggers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"triggers","FullyQualifiedName":"localhost/postgres/triggers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"triggers","FullyQualifiedName":"localhost/postgres/triggers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"triggers","FullyQualifiedName":"localhost/postgres/triggers","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"udt_privileges","FullyQualifiedName":"localhost/postgres/udt_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"udt_privileges","FullyQualifiedName":"localhost/postgres/udt_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"udt_privileges","FullyQualifiedName":"localhost/postgres/udt_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"udt_privileges","FullyQualifiedName":"localhost/postgres/udt_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"udt_privileges","FullyQualifiedName":"localhost/postgres/udt_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"udt_privileges","FullyQualifiedName":"localhost/postgres/udt_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"udt_privileges","FullyQualifiedName":"localhost/postgres/udt_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"usage_privileges","FullyQualifiedName":"localhost/postgres/usage_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"usage_privileges","FullyQualifiedName":"localhost/postgres/usage_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"usage_privileges","FullyQualifiedName":"localhost/postgres/usage_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"usage_privileges","FullyQualifiedName":"localhost/postgres/usage_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"usage_privileges","FullyQualifiedName":"localhost/postgres/usage_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"usage_privileges","FullyQualifiedName":"localhost/postgres/usage_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"usage_privileges","FullyQualifiedName":"localhost/postgres/usage_privileges","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"user_defined_types","FullyQualifiedName":"localhost/postgres/user_defined_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"user_defined_types","FullyQualifiedName":"localhost/postgres/user_defined_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"user_defined_types","FullyQualifiedName":"localhost/postgres/user_defined_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"user_defined_types","FullyQualifiedName":"localhost/postgres/user_defined_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"user_defined_types","FullyQualifiedName":"localhost/postgres/user_defined_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"user_defined_types","FullyQualifiedName":"localhost/postgres/user_defined_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"user_defined_types","FullyQualifiedName":"localhost/postgres/user_defined_types","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"user_mapping_options","FullyQualifiedName":"localhost/postgres/user_mapping_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"user_mapping_options","FullyQualifiedName":"localhost/postgres/user_mapping_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"user_mapping_options","FullyQualifiedName":"localhost/postgres/user_mapping_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"user_mapping_options","FullyQualifiedName":"localhost/postgres/user_mapping_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"user_mapping_options","FullyQualifiedName":"localhost/postgres/user_mapping_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"user_mapping_options","FullyQualifiedName":"localhost/postgres/user_mapping_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"user_mapping_options","FullyQualifiedName":"localhost/postgres/user_mapping_options","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"user_mappings","FullyQualifiedName":"localhost/postgres/user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"user_mappings","FullyQualifiedName":"localhost/postgres/user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"user_mappings","FullyQualifiedName":"localhost/postgres/user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"user_mappings","FullyQualifiedName":"localhost/postgres/user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"user_mappings","FullyQualifiedName":"localhost/postgres/user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"user_mappings","FullyQualifiedName":"localhost/postgres/user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"user_mappings","FullyQualifiedName":"localhost/postgres/user_mappings","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"view_column_usage","FullyQualifiedName":"localhost/postgres/view_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"view_column_usage","FullyQualifiedName":"localhost/postgres/view_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"view_column_usage","FullyQualifiedName":"localhost/postgres/view_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"view_column_usage","FullyQualifiedName":"localhost/postgres/view_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"view_column_usage","FullyQualifiedName":"localhost/postgres/view_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"view_column_usage","FullyQualifiedName":"localhost/postgres/view_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"view_column_usage","FullyQualifiedName":"localhost/postgres/view_column_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"view_routine_usage","FullyQualifiedName":"localhost/postgres/view_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"view_routine_usage","FullyQualifiedName":"localhost/postgres/view_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"view_routine_usage","FullyQualifiedName":"localhost/postgres/view_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"view_routine_usage","FullyQualifiedName":"localhost/postgres/view_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"view_routine_usage","FullyQualifiedName":"localhost/postgres/view_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"view_routine_usage","FullyQualifiedName":"localhost/postgres/view_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"view_routine_usage","FullyQualifiedName":"localhost/postgres/view_routine_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"view_table_usage","FullyQualifiedName":"localhost/postgres/view_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"view_table_usage","FullyQualifiedName":"localhost/postgres/view_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"view_table_usage","FullyQualifiedName":"localhost/postgres/view_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"view_table_usage","FullyQualifiedName":"localhost/postgres/view_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"view_table_usage","FullyQualifiedName":"localhost/postgres/view_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"view_table_usage","FullyQualifiedName":"localhost/postgres/view_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"view_table_usage","FullyQualifiedName":"localhost/postgres/view_table_usage","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}},{"Resource":{"Name":"views","FullyQualifiedName":"localhost/postgres/views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"delete","Parent":null}},{"Resource":{"Name":"views","FullyQualifiedName":"localhost/postgres/views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"insert","Parent":null}},{"Resource":{"Name":"views","FullyQualifiedName":"localhost/postgres/views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"references","Parent":null}},{"Resource":{"Name":"views","FullyQualifiedName":"localhost/postgres/views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"select","Parent":null}},{"Resource":{"Name":"views","FullyQualifiedName":"localhost/postgres/views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"trigger","Parent":null}},{"Resource":{"Name":"views","FullyQualifiedName":"localhost/postgres/views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"truncate","Parent":null}},{"Resource":{"Name":"views","FullyQualifiedName":"localhost/postgres/views","Type":"table","Metadata":{"rows":"Unknown","size":"0 bytes"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"database","Metadata":{"owner":"postgres"},"Parent":{"Name":"postgres","FullyQualifiedName":"localhost/postgres","Type":"user","Metadata":{"role":"postgres"},"Parent":null}}},"Permission":{"Value":"update","Parent":null}}],"UnboundedResources":null,"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/postgres/permissions.go b/pkg/analyzer/analyzers/postgres/permissions.go
deleted file mode 100644
index b8a041dcf3d5..000000000000
--- a/pkg/analyzer/analyzers/postgres/permissions.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package postgres
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- BypassRls Permission = iota
- Connect Permission = iota
- Create Permission = iota
- CreateDb Permission = iota
- CreateRole Permission = iota
- Delete Permission = iota
- InheritanceOfPrivs Permission = iota
- Insert Permission = iota
- Login Permission = iota
- References Permission = iota
- Replication Permission = iota
- Select Permission = iota
- Superuser Permission = iota
- Temp Permission = iota
- Trigger Permission = iota
- Truncate Permission = iota
- Update Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- BypassRls: "bypass_rls",
- Connect: "connect",
- Create: "create",
- CreateDb: "create_db",
- CreateRole: "create_role",
- Delete: "delete",
- InheritanceOfPrivs: "inheritance_of_privs",
- Insert: "insert",
- Login: "login",
- References: "references",
- Replication: "replication",
- Select: "select",
- Superuser: "superuser",
- Temp: "temp",
- Trigger: "trigger",
- Truncate: "truncate",
- Update: "update",
- }
-
- StringToPermission = map[string]Permission{
- "bypass_rls": BypassRls,
- "connect": Connect,
- "create": Create,
- "create_db": CreateDb,
- "create_role": CreateRole,
- "delete": Delete,
- "inheritance_of_privs": InheritanceOfPrivs,
- "insert": Insert,
- "login": Login,
- "references": References,
- "replication": Replication,
- "select": Select,
- "superuser": Superuser,
- "temp": Temp,
- "trigger": Trigger,
- "truncate": Truncate,
- "update": Update,
- }
-
- PermissionIDs = map[Permission]int{
- BypassRls: 1,
- Connect: 2,
- Create: 3,
- CreateDb: 4,
- CreateRole: 5,
- Delete: 6,
- InheritanceOfPrivs: 7,
- Insert: 8,
- Login: 9,
- References: 10,
- Replication: 11,
- Select: 12,
- Superuser: 13,
- Temp: 14,
- Trigger: 15,
- Truncate: 16,
- Update: 17,
- }
-
- IdToPermission = map[int]Permission{
- 1: BypassRls,
- 2: Connect,
- 3: Create,
- 4: CreateDb,
- 5: CreateRole,
- 6: Delete,
- 7: InheritanceOfPrivs,
- 8: Insert,
- 9: Login,
- 10: References,
- 11: Replication,
- 12: Select,
- 13: Superuser,
- 14: Temp,
- 15: Trigger,
- 16: Truncate,
- 17: Update,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/postgres/permissions.yaml b/pkg/analyzer/analyzers/postgres/permissions.yaml
deleted file mode 100644
index 21a81724269e..000000000000
--- a/pkg/analyzer/analyzers/postgres/permissions.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-permissions:
- - bypass_rls
- - connect
- - create
- - create_db
- - create_role
- - delete
- - inheritance_of_privs
- - insert
- - login
- - references
- - replication
- - select
- - superuser
- - temp
- - trigger
- - truncate
- - update
diff --git a/pkg/analyzer/analyzers/postgres/postgres.go b/pkg/analyzer/analyzers/postgres/postgres.go
deleted file mode 100644
index be7cf2666163..000000000000
--- a/pkg/analyzer/analyzers/postgres/postgres.go
+++ /dev/null
@@ -1,655 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go postgres
-
-package postgres
-
-import (
- "database/sql"
- "errors"
- "fmt"
- "os"
- "regexp"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/lib/pq"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypePostgres }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- uri, ok := credInfo["connection_string"]
- if !ok {
- return nil, errors.New("connection string not found in credInfo")
- }
-
- info, err := AnalyzePermissions(a.Cfg, uri)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypePostgres,
- Metadata: nil,
- Bindings: []analyzers.Binding{},
- }
-
- // set user related bindings in result
- userResource, userBindings := bakeUserBindings(info)
- result.Bindings = append(result.Bindings, userBindings...)
-
- // add user's database privileges to bindings
- dbNameToResourceMap, dbBindings := bakeDatabaseBindings(userResource, info)
- result.Bindings = append(result.Bindings, dbBindings...)
-
- // add user's table privileges to bindings
- tableBindings := bakeTableBindings(dbNameToResourceMap, info)
- result.Bindings = append(result.Bindings, tableBindings...)
-
- return &result
-}
-
-func bakeUserBindings(info *SecretInfo) (analyzers.Resource, []analyzers.Binding) {
- userResource := analyzers.Resource{
- Name: info.User,
- FullyQualifiedName: info.Host + "/" + info.User,
- Type: "user",
- Metadata: map[string]any{
- "role": info.Role,
- },
- }
-
- var bindings []analyzers.Binding
-
- for rolePriv, exists := range info.RolePrivs {
- if exists {
- bindings = append(bindings, analyzers.Binding{
- Resource: userResource,
- Permission: analyzers.Permission{
- Value: rolePriv,
- },
- })
- }
- }
-
- return userResource, bindings
-}
-
-func bakeDatabaseBindings(userResource analyzers.Resource, info *SecretInfo) (map[string]*analyzers.Resource, []analyzers.Binding) {
- dbNameToResourceMap := map[string]*analyzers.Resource{}
- dbBindings := []analyzers.Binding{}
-
- for _, db := range info.DBs {
- dbResource := analyzers.Resource{
- Name: db.DatabaseName,
- FullyQualifiedName: info.Host + "/" + db.DatabaseName,
- Type: "database",
- Metadata: map[string]any{
- "owner": db.Owner,
- },
- Parent: &userResource,
- }
-
- // populate map to reference later for tables
- dbNameToResourceMap[db.DatabaseName] = &dbResource
-
- dbPriviliges := map[string]bool{
- "connect": db.Connect,
- "create": db.Create,
- "temp": db.CreateTemp,
- }
-
- for priv, exists := range dbPriviliges {
- if exists {
- dbBindings = append(dbBindings, analyzers.Binding{
- Resource: dbResource,
- Permission: analyzers.Permission{
- Value: priv,
- },
- })
- }
- }
- }
-
- return dbNameToResourceMap, dbBindings
-}
-
-func bakeTableBindings(dbNameToResourceMap map[string]*analyzers.Resource, info *SecretInfo) []analyzers.Binding {
- var tableBindings []analyzers.Binding
-
- for dbName, tableMap := range info.TablePrivs {
- dbResource, ok := dbNameToResourceMap[dbName]
- if !ok {
- continue
- }
-
- for tableName, tableData := range tableMap {
- tableResource := analyzers.Resource{
- Name: tableName,
- FullyQualifiedName: info.Host + "/" + dbResource.Name + "/" + tableName,
- Type: "table",
- Metadata: map[string]any{
- "size": tableData.Size,
- "rows": tableData.Rows,
- },
- Parent: dbResource,
- }
-
- tablePrivsMap := map[string]bool{
- "select": tableData.Privs.Select,
- "insert": tableData.Privs.Insert,
- "update": tableData.Privs.Update,
- "delete": tableData.Privs.Delete,
- "truncate": tableData.Privs.Truncate,
- "references": tableData.Privs.References,
- "trigger": tableData.Privs.Trigger,
- }
-
- for priv, exists := range tablePrivsMap {
- if exists {
- tableBindings = append(tableBindings, analyzers.Binding{
- Resource: tableResource,
- Permission: analyzers.Permission{
- Value: priv,
- },
- })
- }
- }
- }
- }
-
- return tableBindings
-}
-
-type DBPrivs struct {
- Connect bool
- Create bool
- CreateTemp bool
-}
-
-type DB struct {
- DatabaseName string
- Owner string
- DBPrivs
-}
-
-type TablePrivs struct {
- Select bool
- Insert bool
- Update bool
- Delete bool
- Truncate bool
- References bool
- Trigger bool
-}
-
-type TableData struct {
- Size string
- Rows string
- Privs TablePrivs
-}
-
-const (
- pg_connect_timeout = "connect_timeout"
- pg_dbname = "dbname"
- pg_host = "host"
- pg_password = "password"
- pg_port = "port"
- pg_requiressl = "requiressl"
- pg_sslmode = "sslmode"
- pg_sslmode_allow = "allow"
- pg_sslmode_disable = "disable"
- pg_sslmode_prefer = "prefer"
- pg_sslmode_require = "require"
- pg_user = "user"
-)
-
-var connStrPartPattern = regexp.MustCompile(`([[:alpha:]]+)='(.+?)' ?`)
-
-type SecretInfo struct {
- Host string
- User string
- Role string
- RolePrivs map[string]bool
- DBs []DB
- TablePrivs map[string]map[string]*TableData
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, connectionStr string) {
-
- // ToDo: Add in logging
- if cfg.LoggingEnabled {
- color.Red("[x] Logging is not supported for this analyzer.")
- return
- }
-
- info, err := AnalyzePermissions(cfg, connectionStr)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
-
- color.Yellow("[!] Successfully connected to Postgres database.")
- printUserRoleAndPriv(info.Role, info.RolePrivs)
-
- // Print db privs
- if len(info.DBs) > 0 {
- fmt.Print("\n\n")
- color.Green("[i] User has the following database privileges:")
- printDBPrivs(info.DBs, info.User)
- }
-
- // Print table privs
- if len(info.TablePrivs) > 0 {
- fmt.Print("\n\n")
- color.Green("[i] User has the following table privileges:")
- printTablePrivs(info.TablePrivs)
- }
-}
-
-func AnalyzePermissions(cfg *config.Config, connectionStr string) (*SecretInfo, error) {
-
- connStr, err := pq.ParseURL(string(connectionStr))
- if err != nil {
- return nil, fmt.Errorf("failed to parse Postgres connection string: %w", err)
- }
- parts := connStrPartPattern.FindAllStringSubmatch(connStr, -1)
- params := make(map[string]string, len(parts))
- for _, part := range parts {
- params[part[1]] = part[2]
- }
- db, err := createConnection(params, "")
- if err != nil {
- return nil, fmt.Errorf("failed to connect to Postgres database: %w", err)
- }
- defer db.Close()
-
- role, privs, err := getUserPrivs(db)
- if err != nil {
- return nil, fmt.Errorf("failed to retrieve user privileges: %w", err)
- }
- currentUser, dbs, err := getDBPrivs(db)
- if err != nil {
- return nil, fmt.Errorf("failed to retrieve database privileges: %w", err)
- }
- tablePrivs, err := getTablePrivs(params, buildSliceDBNames(dbs))
- if err != nil {
- return nil, fmt.Errorf("failed to retrieve table privileges: %w", err)
- }
-
- return &SecretInfo{
- Host: params[pg_host],
- User: currentUser,
- Role: role,
- RolePrivs: privs,
- DBs: dbs,
- TablePrivs: tablePrivs,
- }, nil
-}
-
-func isErrorDatabaseNotFound(err error, dbName string, user string) bool {
- options := []string{dbName, user, "postgres"}
- for _, option := range options {
- if strings.Contains(err.Error(), fmt.Sprintf("database \"%s\" does not exist", option)) {
- return true
- }
- }
- return false
-}
-
-func createConnection(params map[string]string, database string) (*sql.DB, error) {
- if sslmode := params[pg_sslmode]; sslmode == pg_sslmode_allow || sslmode == pg_sslmode_prefer {
- // pq doesn't support 'allow' or 'prefer'. If we find either of them, we'll just ignore it. This will trigger
- // the same logic that is run if no sslmode is set at all (which mimics 'prefer', which is the default).
- delete(params, pg_sslmode)
- }
-
- var connStr string
- for key, value := range params {
- if database != "" && key == "dbname" {
- connStr += fmt.Sprintf("%s='%s'", key, database)
- } else {
- connStr += fmt.Sprintf("%s='%s'", key, value)
- }
- }
-
- db, err := sql.Open("postgres", connStr)
- if err != nil {
- return nil, err
- }
-
- err = db.Ping()
- switch {
- case err == nil:
- return db, nil
- case strings.Contains(err.Error(), "password authentication failed"):
- return nil, errors.New("password authentication failed")
- case errors.Is(err, pq.ErrSSLNotSupported) && params[pg_sslmode] == "":
- // If the sslmode is unset, then either it was unset in the candidate secret, or we've intentionally unset it
- // because it was specified as 'allow' or 'prefer', neither of which pq supports. In all of these cases, non-SSL
- // connections are acceptable, so now we try a connection without SSL.
- params[pg_sslmode] = pg_sslmode_disable
- defer delete(params, pg_sslmode) // We want to return with the original params map intact (for ExtraData)
- return createConnection(params, database)
- case isErrorDatabaseNotFound(err, params[pg_dbname], params[pg_user]):
- color.Green("[!] Successfully connected to Postgres database.")
- return nil, err
- default:
- return nil, err
- }
-}
-
-func getUserPrivs(db *sql.DB) (string, map[string]bool, error) {
- // Prepare the SQL statement
- query := `SELECT rolname AS role_name,
- rolsuper AS is_superuser,
- rolinherit AS can_inherit,
- rolcreaterole AS can_create_role,
- rolcreatedb AS can_create_db,
- rolcanlogin AS can_login,
- rolreplication AS is_replication_role,
- rolbypassrls AS bypasses_rls
- FROM pg_roles WHERE rolname = current_user;`
-
- // Execute the SQL query
- rows, err := db.Query(query)
- if err != nil {
- return "", nil, err
- }
- defer rows.Close()
-
- var roleName string
- var isSuperuser, canInherit, canCreateRole, canCreateDB, canLogin, isReplicationRole, bypassesRLS bool
- // Iterate over the rows
- for rows.Next() {
- if err := rows.Scan(&roleName, &isSuperuser, &canInherit, &canCreateRole, &canCreateDB, &canLogin, &isReplicationRole, &bypassesRLS); err != nil {
- return "", nil, err
- }
- }
-
- // Check for errors during iteration
- if err := rows.Err(); err != nil {
- return "", nil, err
- }
-
- // Map roles to privileges
- var mapRoles map[string]bool = map[string]bool{
- "Superuser": isSuperuser,
- "Inheritance of Privs": canInherit,
- "Create Role": canCreateRole,
- "Create DB": canCreateDB,
- "Login": canLogin,
- "Replication": isReplicationRole,
- "Bypass RLS": bypassesRLS,
- }
-
- return roleName, mapRoles, nil
-}
-
-func getDBPrivs(db *sql.DB) (string, []DB, error) {
- query := `
- SELECT
- d.datname AS database_name,
- u.usename AS owner,
- current_user AS current_user,
- has_database_privilege(current_user, d.datname, 'CONNECT') AS can_connect,
- has_database_privilege(current_user, d.datname, 'CREATE') AS can_create,
- has_database_privilege(current_user, d.datname, 'TEMP') AS can_create_temporary_tables
- FROM
- pg_database d
- JOIN
- pg_user u ON d.datdba = u.usesysid
- WHERE
- NOT d.datistemplate
- ORDER BY
- d.datname;
- `
- // Originally had WHERE NOT d.datistemplate AND d.datallowconn
-
- // Execute the query
- rows, err := db.Query(query)
- if err != nil {
- return "", nil, err
- }
- defer rows.Close()
-
- dbs := make([]DB, 0)
-
- var currentUser string
- // Iterate through the result set
- for rows.Next() {
- var dbName, owner string
- var canConnect, canCreate, canCreateTemp bool
- err := rows.Scan(&dbName, &owner, ¤tUser, &canConnect, &canCreate, &canCreateTemp)
- if err != nil {
- return "", nil, err
- }
-
- db := DB{
- DatabaseName: dbName,
- Owner: owner,
- DBPrivs: DBPrivs{
- Connect: canConnect,
- Create: canCreate,
- CreateTemp: canCreateTemp,
- },
- }
- dbs = append(dbs, db)
- }
- if err = rows.Err(); err != nil {
- return "", nil, err
- }
-
- return currentUser, dbs, nil
-}
-
-func printDBPrivs(dbs []DB, current_user string) {
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Database", "Owner", "Access Privileges"})
- for _, db := range dbs {
- privs := buildDBPrivsStr(db)
- writer := getDBWriter(db, current_user)
- t.AppendRow([]interface{}{writer(db.DatabaseName), writer(db.Owner), writer(privs)})
- }
- t.Render()
-}
-
-func buildDBPrivsStr(db DB) string {
- privs := ""
- if db.Connect {
- privs += "CONNECT"
- }
- if db.Create {
- privs += ", CREATE"
- }
- if db.CreateTemp {
- privs += ", TEMP"
- }
- privs = strings.TrimPrefix(privs, ", ")
- return privs
-}
-
-func getDBWriter(db DB, current_user string) func(a ...interface{}) string {
- if db.Owner == current_user {
- return analyzers.GreenWriter
- } else if db.Connect && db.Create && db.CreateTemp {
- return analyzers.GreenWriter
- } else if db.Connect || db.Create || db.CreateTemp {
- return analyzers.YellowWriter
- } else {
- return analyzers.DefaultWriter
- }
-}
-
-func buildSliceDBNames(dbs []DB) []string {
- var dbNames []string
- for _, db := range dbs {
- if db.DBPrivs.Connect {
- dbNames = append(dbNames, db.DatabaseName)
- }
- }
- return dbNames
-}
-
-func getTablePrivs(params map[string]string, databases []string) (map[string]map[string]*TableData, error) {
-
- tablePrivileges := make(map[string]map[string]*TableData, 0)
-
- for _, dbase := range databases {
- // Connect to db
- db, err := createConnection(params, dbase)
- if err != nil {
- // color.Red("[x] Failed to connect to Postgres database: %s", dbase)
- continue
- }
- defer db.Close()
-
- // Get table privs
- query := `
- SELECT
- rtg.table_catalog,
- rtg.table_name,
- rtg.privilege_type,
- pg_size_pretty(pg_total_relation_size(pc.oid)) AS table_size,
- pc.reltuples AS estimate
- FROM
- information_schema.role_table_grants rtg
- JOIN
- pg_catalog.pg_class pc ON rtg.table_name = pc.relname
- WHERE
- rtg.grantee = current_user;
-
- `
-
- // Execute the query
- rows, err := db.Query(query)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
-
- // Iterate through the result set
- for rows.Next() {
- var database, table, priv, size, row_count string
- err := rows.Scan(&database, &table, &priv, &size, &row_count)
- if err != nil {
- return nil, err
- }
-
- if _, ok := tablePrivileges[database]; !ok {
- tablePrivileges[database] = map[string]*TableData{
- table: {},
- }
- }
-
- if _, ok := tablePrivileges[database][table]; !ok {
- tablePrivileges[database][table] = &TableData{}
- }
-
- switch priv {
- case "SELECT":
- tablePrivileges[database][table].Privs.Select = true
- case "INSERT":
- tablePrivileges[database][table].Privs.Insert = true
- case "UPDATE":
- tablePrivileges[database][table].Privs.Update = true
- case "DELETE":
- tablePrivileges[database][table].Privs.Delete = true
- case "TRUNCATE":
- tablePrivileges[database][table].Privs.Truncate = true
- case "REFERENCES":
- tablePrivileges[database][table].Privs.References = true
- case "TRIGGER":
- tablePrivileges[database][table].Privs.Trigger = true
- }
- tablePrivileges[database][table].Size = size
- if row_count != "-1" {
- tablePrivileges[database][table].Rows = row_count
- } else {
- tablePrivileges[database][table].Rows = "Unknown"
- }
- }
- if err = rows.Err(); err != nil {
- return nil, err
- }
- db.Close()
- }
-
- return tablePrivileges, nil
-}
-
-func printTablePrivs(tables map[string]map[string]*TableData) {
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Database", "Table", "Access Privileges", "Est. Size", "Est. Rows"})
- var writer func(a ...interface{}) string
- for db, table := range tables {
- for table_name, tableData := range table {
- privs := tableData.Privs
- privsStr := buildTablePrivsStr(privs)
- if privsStr == "" {
- writer = color.New().SprintFunc()
- } else {
- writer = color.New(color.FgGreen).SprintFunc()
- }
- t.AppendRow([]interface{}{writer(db), writer(table_name), writer(privsStr), writer("< " + tableData.Size), writer(tableData.Rows)})
- }
- }
- t.Render()
-}
-
-func printUserRoleAndPriv(role string, privs map[string]bool) {
- color.Yellow("[i] User: %s", role)
- color.Yellow("[i] Privileges: ")
- for role, priv := range privs {
- if role == "Superuser" && priv {
- color.Green(" - %s", role)
- } else if priv {
- color.Yellow(" - %s", role)
- }
- }
-}
-
-func buildTablePrivsStr(privs TablePrivs) string {
- var privsStr string
- if privs.Select {
- privsStr += "SELECT"
- }
- if privs.Insert {
- privsStr += ", INSERT"
- }
- if privs.Update {
- privsStr += ", UPDATE"
- }
- if privs.Delete {
- privsStr += ", DELETE"
- }
- if privs.Truncate {
- privsStr += ", TRUNCATE"
- }
- if privs.References {
- privsStr += ", REFERENCES"
- }
- if privs.Trigger {
- privsStr += ", TRIGGER"
- }
- privsStr = strings.TrimPrefix(privsStr, ", ")
- return privsStr
-}
diff --git a/pkg/analyzer/analyzers/postgres/postgres_test.go b/pkg/analyzer/analyzers/postgres/postgres_test.go
deleted file mode 100644
index ab54f8dbf891..000000000000
--- a/pkg/analyzer/analyzers/postgres/postgres_test.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package postgres
-
-import (
- "bytes"
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "os/exec"
- "sort"
- "strings"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-const (
- postgresUser = "postgres"
- postgresPass = "23201da=b56ca236f3dc6736c0f9afad"
- postgresHost = "localhost"
- postgresPort = "5434" // Do not use 5433, as local dev environments can use it for other things
- defaultPort = "5432"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- if err := startPostgres(); err != nil {
- if exitErr, ok := err.(*exec.ExitError); ok {
- t.Fatalf("could not start local postgres: %v w/stderr:\n%s", err, string(exitErr.Stderr))
- } else {
- t.Fatalf("could not start local postgres: %v", err)
- }
- }
- defer stopPostgres()
-
- tests := []struct {
- name string
- connectionString string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid Postgres connection",
- connectionString: fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres`, postgresUser, postgresPass, postgresHost, postgresPort),
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(context.Background(), map[string]string{"connection_string": tt.connectionString})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal(tt.want, &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
-
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
-
-var postgresDockerHash string
-
-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() {
- err := exec.Command("docker", "kill", postgresDockerHash).Run()
- if err != nil {
- fmt.Println("could not stop postgres container:", err)
- }
-}
diff --git a/pkg/analyzer/analyzers/posthog/expected_output.json b/pkg/analyzer/analyzers/posthog/expected_output.json
deleted file mode 100644
index 56fc217c4808..000000000000
--- a/pkg/analyzer/analyzers/posthog/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":39,"Bindings":[{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"action:read","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"activity_log:read","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"annotation:read","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"dashboard:read","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"event_definition:read","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"event_definition:write","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"export:read","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"group:read","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"group:write","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"insight:read","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"person:read","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"person:write","Parent":null}},{"Resource":{"Name":"Default project","FullyQualifiedName":"150774","Type":"project","Metadata":null,"Parent":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null}},"Permission":{"Value":"query:read","Parent":null}},{"Resource":{"Name":"Truffle Security","FullyQualifiedName":"019666bb-9f8e-0000-8bc2-4ea34ec57752","Type":"user","Metadata":null,"Parent":null},"Permission":{"Value":"user:read","Parent":null}},{"Resource":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null},"Permission":{"Value":"organization:read","Parent":null}},{"Resource":{"Name":"TruffleSecurity","FullyQualifiedName":"019666bb-9f89-0000-0820-312e5f974324","Type":"organization","Metadata":null,"Parent":null},"Permission":{"Value":"project:read","Parent":null}}],"UnboundedResources":null,"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/posthog/permissions.go b/pkg/analyzer/analyzers/posthog/permissions.go
deleted file mode 100644
index 9354fbdcecd0..000000000000
--- a/pkg/analyzer/analyzers/posthog/permissions.go
+++ /dev/null
@@ -1,356 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package posthog
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- ActionRead Permission = iota
- ActionWrite Permission = iota
- ActivityLogRead Permission = iota
- ActivityLogWrite Permission = iota
- AnnotationRead Permission = iota
- AnnotationWrite Permission = iota
- BatchExportRead Permission = iota
- BatchExportWrite Permission = iota
- CohortRead Permission = iota
- CohortWrite Permission = iota
- DashboardRead Permission = iota
- DashboardWrite Permission = iota
- DashboardTemplateRead Permission = iota
- DashboardTemplateWrite Permission = iota
- EarlyAccessFeatureRead Permission = iota
- EarlyAccessFeatureWrite Permission = iota
- EventDefinitionRead Permission = iota
- EventDefinitionWrite Permission = iota
- ErrorTrackingRead Permission = iota
- ErrorTrackingWrite Permission = iota
- ExperimentRead Permission = iota
- ExperimentWrite Permission = iota
- ExportRead Permission = iota
- ExportWrite Permission = iota
- FeatureFlagRead Permission = iota
- FeatureFlagWrite Permission = iota
- GroupRead Permission = iota
- GroupWrite Permission = iota
- HogFunctionRead Permission = iota
- HogFunctionWrite Permission = iota
- InsightRead Permission = iota
- InsightWrite Permission = iota
- NotebookRead Permission = iota
- NotebookWrite Permission = iota
- OrganizationRead Permission = iota
- OrganizationWrite Permission = iota
- OrganizationMemberRead Permission = iota
- OrganizationMemberWrite Permission = iota
- PersonRead Permission = iota
- PersonWrite Permission = iota
- PluginRead Permission = iota
- PluginWrite Permission = iota
- ProjectRead Permission = iota
- ProjectWrite Permission = iota
- PropertyDefinitionRead Permission = iota
- PropertyDefinitionWrite Permission = iota
- QueryRead Permission = iota
- SessionRecordingRead Permission = iota
- SessionRecordingWrite Permission = iota
- SessionRecordingPlaylistRead Permission = iota
- SessionRecordingPlaylistWrite Permission = iota
- SharingConfigurationRead Permission = iota
- SharingConfigurationWrite Permission = iota
- SubscriptionRead Permission = iota
- SubscriptionWrite Permission = iota
- SurveyRead Permission = iota
- SurveyWrite Permission = iota
- UserRead Permission = iota
- WebhookRead Permission = iota
- WebhookWrite Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- ActionRead: "action:read",
- ActionWrite: "action:write",
- ActivityLogRead: "activity_log:read",
- ActivityLogWrite: "activity_log:write",
- AnnotationRead: "annotation:read",
- AnnotationWrite: "annotation:write",
- BatchExportRead: "batch_export:read",
- BatchExportWrite: "batch_export:write",
- CohortRead: "cohort:read",
- CohortWrite: "cohort:write",
- DashboardRead: "dashboard:read",
- DashboardWrite: "dashboard:write",
- DashboardTemplateRead: "dashboard_template:read",
- DashboardTemplateWrite: "dashboard_template:write",
- EarlyAccessFeatureRead: "early_access_feature:read",
- EarlyAccessFeatureWrite: "early_access_feature:write",
- EventDefinitionRead: "event_definition:read",
- EventDefinitionWrite: "event_definition:write",
- ErrorTrackingRead: "error_tracking:read",
- ErrorTrackingWrite: "error_tracking:write",
- ExperimentRead: "experiment:read",
- ExperimentWrite: "experiment:write",
- ExportRead: "export:read",
- ExportWrite: "export:write",
- FeatureFlagRead: "feature_flag:read",
- FeatureFlagWrite: "feature_flag:write",
- GroupRead: "group:read",
- GroupWrite: "group:write",
- HogFunctionRead: "hog_function:read",
- HogFunctionWrite: "hog_function:write",
- InsightRead: "insight:read",
- InsightWrite: "insight:write",
- NotebookRead: "notebook:read",
- NotebookWrite: "notebook:write",
- OrganizationRead: "organization:read",
- OrganizationWrite: "organization:write",
- OrganizationMemberRead: "organization_member:read",
- OrganizationMemberWrite: "organization_member:write",
- PersonRead: "person:read",
- PersonWrite: "person:write",
- PluginRead: "plugin:read",
- PluginWrite: "plugin:write",
- ProjectRead: "project:read",
- ProjectWrite: "project:write",
- PropertyDefinitionRead: "property_definition:read",
- PropertyDefinitionWrite: "property_definition:write",
- QueryRead: "query:read",
- SessionRecordingRead: "session_recording:read",
- SessionRecordingWrite: "session_recording:write",
- SessionRecordingPlaylistRead: "session_recording_playlist:read",
- SessionRecordingPlaylistWrite: "session_recording_playlist:write",
- SharingConfigurationRead: "sharing_configuration:read",
- SharingConfigurationWrite: "sharing_configuration:write",
- SubscriptionRead: "subscription:read",
- SubscriptionWrite: "subscription:write",
- SurveyRead: "survey:read",
- SurveyWrite: "survey:write",
- UserRead: "user:read",
- WebhookRead: "webhook:read",
- WebhookWrite: "webhook:write",
- }
-
- StringToPermission = map[string]Permission{
- "action:read": ActionRead,
- "action:write": ActionWrite,
- "activity_log:read": ActivityLogRead,
- "activity_log:write": ActivityLogWrite,
- "annotation:read": AnnotationRead,
- "annotation:write": AnnotationWrite,
- "batch_export:read": BatchExportRead,
- "batch_export:write": BatchExportWrite,
- "cohort:read": CohortRead,
- "cohort:write": CohortWrite,
- "dashboard:read": DashboardRead,
- "dashboard:write": DashboardWrite,
- "dashboard_template:read": DashboardTemplateRead,
- "dashboard_template:write": DashboardTemplateWrite,
- "early_access_feature:read": EarlyAccessFeatureRead,
- "early_access_feature:write": EarlyAccessFeatureWrite,
- "event_definition:read": EventDefinitionRead,
- "event_definition:write": EventDefinitionWrite,
- "error_tracking:read": ErrorTrackingRead,
- "error_tracking:write": ErrorTrackingWrite,
- "experiment:read": ExperimentRead,
- "experiment:write": ExperimentWrite,
- "export:read": ExportRead,
- "export:write": ExportWrite,
- "feature_flag:read": FeatureFlagRead,
- "feature_flag:write": FeatureFlagWrite,
- "group:read": GroupRead,
- "group:write": GroupWrite,
- "hog_function:read": HogFunctionRead,
- "hog_function:write": HogFunctionWrite,
- "insight:read": InsightRead,
- "insight:write": InsightWrite,
- "notebook:read": NotebookRead,
- "notebook:write": NotebookWrite,
- "organization:read": OrganizationRead,
- "organization:write": OrganizationWrite,
- "organization_member:read": OrganizationMemberRead,
- "organization_member:write": OrganizationMemberWrite,
- "person:read": PersonRead,
- "person:write": PersonWrite,
- "plugin:read": PluginRead,
- "plugin:write": PluginWrite,
- "project:read": ProjectRead,
- "project:write": ProjectWrite,
- "property_definition:read": PropertyDefinitionRead,
- "property_definition:write": PropertyDefinitionWrite,
- "query:read": QueryRead,
- "session_recording:read": SessionRecordingRead,
- "session_recording:write": SessionRecordingWrite,
- "session_recording_playlist:read": SessionRecordingPlaylistRead,
- "session_recording_playlist:write": SessionRecordingPlaylistWrite,
- "sharing_configuration:read": SharingConfigurationRead,
- "sharing_configuration:write": SharingConfigurationWrite,
- "subscription:read": SubscriptionRead,
- "subscription:write": SubscriptionWrite,
- "survey:read": SurveyRead,
- "survey:write": SurveyWrite,
- "user:read": UserRead,
- "webhook:read": WebhookRead,
- "webhook:write": WebhookWrite,
- }
-
- PermissionIDs = map[Permission]int{
- ActionRead: 1,
- ActionWrite: 2,
- ActivityLogRead: 3,
- ActivityLogWrite: 4,
- AnnotationRead: 5,
- AnnotationWrite: 6,
- BatchExportRead: 7,
- BatchExportWrite: 8,
- CohortRead: 9,
- CohortWrite: 10,
- DashboardRead: 11,
- DashboardWrite: 12,
- DashboardTemplateRead: 13,
- DashboardTemplateWrite: 14,
- EarlyAccessFeatureRead: 15,
- EarlyAccessFeatureWrite: 16,
- EventDefinitionRead: 17,
- EventDefinitionWrite: 18,
- ErrorTrackingRead: 19,
- ErrorTrackingWrite: 20,
- ExperimentRead: 21,
- ExperimentWrite: 22,
- ExportRead: 23,
- ExportWrite: 24,
- FeatureFlagRead: 25,
- FeatureFlagWrite: 26,
- GroupRead: 27,
- GroupWrite: 28,
- HogFunctionRead: 29,
- HogFunctionWrite: 30,
- InsightRead: 31,
- InsightWrite: 32,
- NotebookRead: 33,
- NotebookWrite: 34,
- OrganizationRead: 35,
- OrganizationWrite: 36,
- OrganizationMemberRead: 37,
- OrganizationMemberWrite: 38,
- PersonRead: 39,
- PersonWrite: 40,
- PluginRead: 41,
- PluginWrite: 42,
- ProjectRead: 43,
- ProjectWrite: 44,
- PropertyDefinitionRead: 45,
- PropertyDefinitionWrite: 46,
- QueryRead: 47,
- SessionRecordingRead: 48,
- SessionRecordingWrite: 49,
- SessionRecordingPlaylistRead: 50,
- SessionRecordingPlaylistWrite: 51,
- SharingConfigurationRead: 52,
- SharingConfigurationWrite: 53,
- SubscriptionRead: 54,
- SubscriptionWrite: 55,
- SurveyRead: 56,
- SurveyWrite: 57,
- UserRead: 58,
- WebhookRead: 59,
- WebhookWrite: 60,
- }
-
- IdToPermission = map[int]Permission{
- 1: ActionRead,
- 2: ActionWrite,
- 3: ActivityLogRead,
- 4: ActivityLogWrite,
- 5: AnnotationRead,
- 6: AnnotationWrite,
- 7: BatchExportRead,
- 8: BatchExportWrite,
- 9: CohortRead,
- 10: CohortWrite,
- 11: DashboardRead,
- 12: DashboardWrite,
- 13: DashboardTemplateRead,
- 14: DashboardTemplateWrite,
- 15: EarlyAccessFeatureRead,
- 16: EarlyAccessFeatureWrite,
- 17: EventDefinitionRead,
- 18: EventDefinitionWrite,
- 19: ErrorTrackingRead,
- 20: ErrorTrackingWrite,
- 21: ExperimentRead,
- 22: ExperimentWrite,
- 23: ExportRead,
- 24: ExportWrite,
- 25: FeatureFlagRead,
- 26: FeatureFlagWrite,
- 27: GroupRead,
- 28: GroupWrite,
- 29: HogFunctionRead,
- 30: HogFunctionWrite,
- 31: InsightRead,
- 32: InsightWrite,
- 33: NotebookRead,
- 34: NotebookWrite,
- 35: OrganizationRead,
- 36: OrganizationWrite,
- 37: OrganizationMemberRead,
- 38: OrganizationMemberWrite,
- 39: PersonRead,
- 40: PersonWrite,
- 41: PluginRead,
- 42: PluginWrite,
- 43: ProjectRead,
- 44: ProjectWrite,
- 45: PropertyDefinitionRead,
- 46: PropertyDefinitionWrite,
- 47: QueryRead,
- 48: SessionRecordingRead,
- 49: SessionRecordingWrite,
- 50: SessionRecordingPlaylistRead,
- 51: SessionRecordingPlaylistWrite,
- 52: SharingConfigurationRead,
- 53: SharingConfigurationWrite,
- 54: SubscriptionRead,
- 55: SubscriptionWrite,
- 56: SurveyRead,
- 57: SurveyWrite,
- 58: UserRead,
- 59: WebhookRead,
- 60: WebhookWrite,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/posthog/permissions.yaml b/pkg/analyzer/analyzers/posthog/permissions.yaml
deleted file mode 100644
index cdea227b5a0e..000000000000
--- a/pkg/analyzer/analyzers/posthog/permissions.yaml
+++ /dev/null
@@ -1,61 +0,0 @@
-permissions:
- - action:read
- - action:write
- - activity_log:read
- - activity_log:write
- - annotation:read
- - annotation:write
- - batch_export:read
- - batch_export:write
- - cohort:read
- - cohort:write
- - dashboard:read
- - dashboard:write
- - dashboard_template:read
- - dashboard_template:write
- - early_access_feature:read
- - early_access_feature:write
- - event_definition:read
- - event_definition:write
- - error_tracking:read
- - error_tracking:write
- - experiment:read
- - experiment:write
- - export:read
- - export:write
- - feature_flag:read
- - feature_flag:write
- - group:read
- - group:write
- - hog_function:read
- - hog_function:write
- - insight:read
- - insight:write
- - notebook:read
- - notebook:write
- - organization:read
- - organization:write
- - organization_member:read
- - organization_member:write
- - person:read
- - person:write
- - plugin:read
- - plugin:write
- - project:read
- - project:write
- - property_definition:read
- - property_definition:write
- - query:read
- - session_recording:read
- - session_recording:write
- - session_recording_playlist:read
- - session_recording_playlist:write
- - sharing_configuration:read
- - sharing_configuration:write
- - subscription:read
- - subscription:write
- - survey:read
- - survey:write
- - user:read
- - webhook:read
- - webhook:write
diff --git a/pkg/analyzer/analyzers/posthog/posthog.go b/pkg/analyzer/analyzers/posthog/posthog.go
deleted file mode 100644
index 6efe7119ca49..000000000000
--- a/pkg/analyzer/analyzers/posthog/posthog.go
+++ /dev/null
@@ -1,545 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go posthog
-
-package posthog
-
-import (
- "bytes"
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "os"
- "strconv"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-const (
- USDomain = "https://us.posthog.com"
- EUDomain = "https://eu.posthog.com"
-)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypePosthog }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("missing key in credInfo")
- }
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypePosthog,
- Metadata: nil,
- Bindings: make([]analyzers.Binding, 0),
- }
-
- if info.orgPermissions == nil {
- // no permissions to check
- return &result
- }
-
- if info.user != nil {
- // for user resource
- userResource := analyzers.Resource{
- Name: info.user.FirstName + " " + info.user.LastName,
- FullyQualifiedName: info.user.UUID,
- Type: "user",
- }
- analyzerPermission := analyzers.Permission{
- Value: PermissionStrings[UserRead],
- }
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: userResource,
- Permission: analyzerPermission,
- })
- }
-
- // for organization permissions, we need to bind the permissions to the organization resource
- organizationResource := analyzers.Resource{
- Name: info.organization.Name,
- FullyQualifiedName: info.organization.ID,
- Type: "organization",
- }
- for _, permission := range info.orgPermissions {
- if value, ok := PermissionStrings[permission]; ok {
- analyzerPermission := analyzers.Permission{
- Value: value,
- }
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: organizationResource,
- Permission: analyzerPermission,
- })
- }
- }
-
- // for project permissions, we need to bind the permissions to the project resource and organization as the parent resource
- for _, projectPermission := range info.projectPermissions {
- projectResource := analyzers.Resource{
- Name: projectPermission.Project.Name,
- FullyQualifiedName: strconv.FormatInt(projectPermission.Project.ID, 10),
- Type: "project",
- Parent: &organizationResource,
- }
- for _, permission := range projectPermission.Permissions {
- permissionStr, _ := permission.ToString()
- analyzerPermission := analyzers.Permission{
- Value: permissionStr,
- }
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: projectResource,
- Permission: analyzerPermission,
- })
- }
- }
-
- return &result
-}
-
-//go:embed scopes.json
-var scopesConfigBytes []byte
-
-type HttpStatusTest struct {
- Endpoint string `json:"endpoint"`
- Method string `json:"method"`
- Payload interface{} `json:"payload"`
- ValidStatuses []int `json:"valid_status_code"`
- InvalidStatuses []int `json:"invalid_status_code"`
-}
-
-func StatusContains(status int, vals []int) bool {
- for _, v := range vals {
- if status == v {
- return true
- }
- }
- return false
-}
-
-func (h *HttpStatusTest) RunTest(cfg *config.Config, client *http.Client, domain string, headers map[string]string, args ...any) (bool, error) {
- // If body data, marshal to JSON
- var data io.Reader
- if h.Payload != nil {
- jsonData, err := json.Marshal(h.Payload)
- if err != nil {
- return false, err
- }
- data = bytes.NewBuffer(jsonData)
- }
-
- req, err := http.NewRequest(h.Method, fmt.Sprintf(domain+h.Endpoint, args...), data)
- if err != nil {
- return false, err
- }
-
- // Add custom headers if provided
- for key, value := range headers {
- req.Header.Set(key, value)
- }
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer resp.Body.Close()
-
- // Check response status code
- switch {
- case StatusContains(resp.StatusCode, h.ValidStatuses):
- return true, nil
- case StatusContains(resp.StatusCode, h.InvalidStatuses):
- return false, nil
- default:
- fmt.Println(h.Method, h.Endpoint)
- return false, errors.New("error checking response status code")
- }
-}
-
-type ScopesConfig struct {
- GeneralScopes []Scope `json:"general_scopes"`
- OrganizationScopes []Scope `json:"organization_scopes"`
- ProjectScopes []Scope `json:"project_scopes"`
-}
-
-type Scope struct {
- Name string `json:"name"`
- Test ScopeTest `json:"test"`
-}
-
-type ScopeTest struct {
- Read *HttpStatusTest `json:"read"`
- Write *HttpStatusTest `json:"write"`
-}
-
-func readInScopesConfig() (*ScopesConfig, error) {
- var scopesConfig ScopesConfig
- if err := json.Unmarshal(scopesConfigBytes, &scopesConfig); err != nil {
- return nil, err
- }
-
- return &scopesConfig, nil
-}
-
-func checkPermissions(cfg *config.Config, client *http.Client, domain string, key string, scopes []Scope, args ...any) ([]Permission, error) {
-
- permissions := make([]Permission, 0)
- headers := map[string]string{"Authorization": "Bearer " + key}
- for _, scope := range scopes {
- var status bool
- var err error
- if scope.Test.Write != nil {
- status, err = scope.Test.Write.RunTest(cfg, client, domain, headers, args...)
- if err != nil {
- return nil, fmt.Errorf("running test: %w", err)
- }
- }
- if status {
- if permission, ok := StringToPermission[scope.Name+":write"]; ok {
- permissions = append(permissions, permission)
- }
- // if write exists, read also exists
- if permission, ok := StringToPermission[scope.Name+":read"]; ok {
- permissions = append(permissions, permission)
- }
- } else {
- status, err = scope.Test.Read.RunTest(cfg, client, domain, headers, args...)
- if err != nil {
- return nil, fmt.Errorf("running test: %w", err)
- }
- if status {
- if permission, ok := StringToPermission[scope.Name+":read"]; ok {
- permissions = append(permissions, permission)
- }
- }
- }
-
- }
-
- return permissions, nil
-}
-
-type ProjectPermissions struct {
- Project *Project
- Permissions []Permission
-}
-
-type SecretInfo struct {
- user *User
- organization *Organization
- orgPermissions []Permission
- projectPermissions []ProjectPermissions
- // generalPermissions []Permission
- unverifiedPermissions map[Permission]struct{}
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error : %s", err.Error())
- return
- }
-
- color.Green("[!] Valid Posthog API key")
- color.Yellow("[i] Expires: Never")
- if info.user != nil {
- printUser(*info.user)
- }
-
- if info.organization == nil {
- color.Yellow("\n[i] No permissions were verified for this key because the key does not have one of the necessary permissions (user:read or organization:read) required to verify other permissions.")
- }
-
- if info.orgPermissions != nil {
- printOrganizationPermissions(*info.organization, info.orgPermissions)
- }
- if len(info.projectPermissions) > 0 {
- printProjectPermissions(info.projectPermissions)
- }
- printUnverifiedPermissions(info.unverifiedPermissions)
-
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- var info = &SecretInfo{}
-
- // These are permissions that cannot be verified due to no endpoint available
- info.unverifiedPermissions = map[Permission]struct{}{
- ErrorTrackingRead: {},
- ErrorTrackingWrite: {},
- SharingConfigurationRead: {},
- SharingConfigurationWrite: {},
- WebhookRead: {},
- WebhookWrite: {},
- }
-
- client := analyzers.NewAnalyzeClient(cfg)
-
- // we need to determine if the key is for US or EU domain
- domain, user, err := resolveDomainAndUser(cfg, client, key)
- if err != nil {
- return nil, fmt.Errorf("Invalid API Key: %w", err)
- }
-
- info.user = user
-
- // Most posthog API scopes are bound to projects and organization, so to determine the scopes we need to first get the organization and projects.
- // If the key has user:read scope, we will get the user above which contains the organizations and projects.
- // If the key does not have user:read scope, we can call the /organizations/@current endpoint to get the
- // organization and projects. If the key does not have organization:read scope as well, we cannot determine any scope.
- var org *Organization
- if user == nil {
- org, err = getOrganization(cfg, client, domain, key)
- if err != nil {
- return nil, err
- }
- if org == nil {
- // can't determine any scopes
- for permission := range PermissionStrings {
- info.unverifiedPermissions[permission] = struct{}{}
- }
- return info, nil
- }
- } else {
- org = &user.Organization
- }
-
- // set the organization in the info struct
- info.organization = org
-
- // read in scopes
- scopesConfig, err := readInScopesConfig()
- if err != nil {
- return nil, err
- }
-
- // check organization permissions
- organizationPermissions, err := checkOrganizationPermissions(cfg, client, domain, key, scopesConfig, org)
- if err != nil {
- return nil, err
- }
-
- // check general permissions
- generalOrganizationPermissions, err := checkGeneralPermissions(cfg, client, domain, key, scopesConfig)
- if err != nil {
- return nil, err
- }
-
- // merge general permissions with organization permissions
- info.orgPermissions = organizationPermissions
- info.orgPermissions = append(info.orgPermissions, generalOrganizationPermissions...)
-
- // check project permissions
- projectPermissions, err := checkProjectPermissions(cfg, client, domain, key, scopesConfig, org)
- if err != nil {
- return nil, err
- }
- info.projectPermissions = projectPermissions
-
- return info, nil
-}
-
-func checkGeneralPermissions(cfg *config.Config, client *http.Client, domain, key string, scopesConfig *ScopesConfig) ([]Permission, error) {
- return checkPermissions(cfg, client, domain, key, scopesConfig.GeneralScopes)
-}
-
-func checkOrganizationPermissions(
- cfg *config.Config,
- client *http.Client,
- domain,
- key string,
- scopesConfig *ScopesConfig,
- org *Organization,
-) ([]Permission, error) {
- return checkPermissions(cfg, client, domain, key, scopesConfig.OrganizationScopes, org.ID)
-}
-
-func checkProjectPermissions(
- cfg *config.Config,
- client *http.Client,
- domain,
- key string,
- scopesConfig *ScopesConfig,
- org *Organization,
-) ([]ProjectPermissions, error) {
- projectPermissions := make([]ProjectPermissions, 0)
- for _, project := range org.Projects {
- projectPermission := ProjectPermissions{
- Project: &project,
- }
- permissions, err := checkPermissions(cfg, client, domain, key, scopesConfig.ProjectScopes, project.ID)
- if err != nil {
- return nil, err
- }
- projectPermission.Permissions = permissions
- projectPermissions = append(projectPermissions, projectPermission)
- }
- return projectPermissions, nil
-}
-
-type User struct {
- UUID string `json:"uuid"`
- FirstName string `json:"first_name"`
- LastName string `json:"last_name"`
- Email string `json:"email"`
- Organization Organization `json:"organization"`
-}
-
-type Organization struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Projects []Project `json:"projects"`
-}
-
-type Project struct {
- ID int64 `json:"id"`
- Name string `json:"name"`
-}
-
-// resolves the domain and user (if permission exists) by calling the /users/@me method for both US and EU domains
-// if the response is 200 OK, it means the domain is valid and user:read permission is also there
-// if the response is 403 Forbidden, it means the domain is valid but user:read permission is not there
-// if the response is 401 Unauthorized, it means the domain is invalid
-func resolveDomainAndUser(cfg *config.Config, client *http.Client, key string) (string, *User, error) {
-
- domains := []string{USDomain, EUDomain}
- for _, domain := range domains {
- req, err := http.NewRequest(http.MethodGet, domain+"/api/users/@me/", nil)
- if err != nil {
- return "", nil, err
- }
- req.Header.Set("Authorization", "Bearer "+key)
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return "", nil, err
- }
- defer resp.Body.Close()
-
- switch resp.StatusCode {
- case http.StatusOK:
- // domain is valid and user permission also exists
- var userInfo User
- if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
- return "", nil, err
- }
- return domain, &userInfo, nil
- case http.StatusForbidden:
- // domain is valid but user permission does not exist
- return domain, nil, nil
- case http.StatusUnauthorized:
- // Key might not be valid of this domain
- // Try the other domain
- continue
- default:
- // unexpected status code
- return "", nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
- }
- return "", nil, fmt.Errorf("invalid Posthog API key")
-}
-
-func getOrganization(cfg *config.Config, client *http.Client, domain string, key string) (*Organization, error) {
- req, err := http.NewRequest(http.MethodGet, domain+"/api/organizations/@current/", nil)
- if err != nil {
- return nil, err
- }
- req.Header.Set("Authorization", "Bearer "+key)
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- switch resp.StatusCode {
- case http.StatusOK:
- var org Organization
- if err := json.NewDecoder(resp.Body).Decode(&org); err != nil {
- return nil, err
- }
- return &org, nil
- case http.StatusForbidden:
- return nil, nil
- default:
- return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-func printUser(user User) {
- color.Yellow("\n[i] User Info:")
- color.Green("[i] Name: %s %s", user.FirstName, user.LastName)
- color.Green("[i] Email: %s", user.Email)
- color.Green("[i] ID: %s", user.UUID)
-}
-
-func printOrganizationPermissions(organization Organization, permissions []Permission) {
- color.Yellow("\n[i] Organization Permissions:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Organization", "Permission"})
- permissionsString := make([]string, len(permissions))
- for i, permission := range permissions {
- permissionsString[i], _ = permission.ToString()
- }
- t.AppendRow(table.Row{
- color.GreenString(organization.Name),
- color.GreenString(strings.Join(permissionsString, "\n")),
- })
- t.Render()
-}
-
-func printProjectPermissions(projectPermissions []ProjectPermissions) {
- color.Yellow("\n[i] Project Permissions:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Project", "Permission"})
- for _, projectPermission := range projectPermissions {
- permissionsString := make([]string, len(projectPermission.Permissions))
- for i, permission := range projectPermission.Permissions {
- permissionsString[i], _ = permission.ToString()
-
- }
- t.AppendRow(table.Row{
- color.GreenString(projectPermission.Project.Name),
- color.GreenString(strings.Join(permissionsString, "\n")),
- })
- }
- t.Render()
-}
-
-func printUnverifiedPermissions(permissions map[Permission]struct{}) {
- color.Yellow("\n[i] Unverified Permissions:")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Permission"})
- for permission := range permissions {
- permissionStr, _ := permission.ToString()
- t.AppendRow(table.Row{color.YellowString(permissionStr)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/posthog/posthog_test.go b/pkg/analyzer/analyzers/posthog/posthog_test.go
deleted file mode 100644
index b7436898680b..000000000000
--- a/pkg/analyzer/analyzers/posthog/posthog_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package posthog
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid posthog api key",
- key: testSecrets.MustGetField("POSTHOG_API_KEY"),
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/posthog/scopes.json b/pkg/analyzer/analyzers/posthog/scopes.json
deleted file mode 100644
index ee20081a25df..000000000000
--- a/pkg/analyzer/analyzers/posthog/scopes.json
+++ /dev/null
@@ -1,668 +0,0 @@
-{
- "general_scopes": [
- {
- "name": "organization",
- "test": {
- "read": {
- "endpoint": "/api/organizations",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/organizations",
- "method": "POST",
- "valid_status_code": [
- 400
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- }
- ],
- "organization_scopes": [
- {
- "name": "batch_export",
- "test": {
- "read": {
- "endpoint": "/api/organizations/%s/batch_exports",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/organizations/%s/batch_exports",
- "method": "POST",
- "valid_status_code": [
- 400
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "organization_member",
- "test": {
- "read": {
- "endpoint": "/api/organizations/%s/members",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/organizations/%s/members/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 500
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "project",
- "test": {
- "read": {
- "endpoint": "/api/organizations/%s/projects",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/organizations/%s/projects/`nowaythiscanexist",
- "method": "DELETE",
- "valid_status_code": [
- 400
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- }
- ],
- "project_scopes": [
- {
- "name": "action",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/actions",
- "method": "GET",
- "valid_status_code": [200],
- "invalid_status_code": [403]
- },
- "write": {
- "endpoint": "/api/projects/%d/actions",
- "method": "POST",
- "valid_status_code": [500],
- "invalid_status_code": [403]
- }
- }
- },
- {
- "name": "activity_log",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/activity_log",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/activity_log",
- "method": "POST",
- "valid_status_code": [
- 500
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "annotation",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/annotations",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/annotations/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 404
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "cohort",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/cohorts",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/cohorts/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 404
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "dashboard",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/dashboards",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/dashboards/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 500
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "dashboard_template",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/dashboard_templates",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/dashboard_templates/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 404
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "early_access_feature",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/early_access_feature",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/early_access_feature",
- "method": "POST",
- "valid_status_code": [
- 400
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "event_definition",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/event_definitions",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/event_definitions/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 500
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "experiment",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/experiments",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/experiments",
- "method": "POST",
- "valid_status_code": [
- 400
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "export",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/exports",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/exports",
- "method": "POST",
- "valid_status_code": [
- 400
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "feature_flag",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/feature_flags",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/feature_flags",
- "method": "POST",
- "valid_status_code": [
- 400
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "group",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/groups",
- "method": "GET",
- "valid_status_code": [
- 400
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/groups/update_property",
- "method": "POST",
- "valid_status_code": [
- 500
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "hog_function",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/hog_functions",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/hog_functions/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 404
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "insight",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/insights",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/insights/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 404
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "notebook",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/notebooks",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/notebooks/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 404
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "person",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/persons",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/persons/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 400
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "plugin",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/plugin_configs",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/plugin_configs",
- "method": "POST",
- "valid_status_code": [
- 400
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "property_definition",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/property_definitions",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/property_definitions/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 500
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "query",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/query/`nowaythiscanexist",
- "method": "GET",
- "valid_status_code": [
- 404
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "session_recording",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/session_recordings",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/session_recordings/`nowaythisexists",
- "method": "PATCH",
- "valid_status_code": [
- 404
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "session_recording_playlist",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/session_recording_playlists",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/session_recording_playlists/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 404
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "subscription",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/subscriptions",
- "method": "GET",
- "valid_status_code": [
- 200,
- 402
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/subscriptions/`nowaythiscanexist",
- "method": "PATCH",
- "valid_status_code": [
- 402,
- 404
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- },
- {
- "name": "survey",
- "test": {
- "read": {
- "endpoint": "/api/projects/%d/surveys",
- "method": "GET",
- "valid_status_code": [
- 200
- ],
- "invalid_status_code": [
- 403
- ]
- },
- "write": {
- "endpoint": "/api/projects/%d/surveys",
- "method": "POST",
- "valid_status_code": [
- 400
- ],
- "invalid_status_code": [
- 403
- ]
- }
- }
- }
- ]
-
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/postman/expected_output.json b/pkg/analyzer/analyzers/postman/expected_output.json
deleted file mode 100644
index 30795024864b..000000000000
--- a/pkg/analyzer/analyzers/postman/expected_output.json
+++ /dev/null
@@ -1,76 +0,0 @@
-{
- "AnalyzerType": 13,
- "Bindings": [
- {
- "Resource": {
- "Name": "rendy",
- "FullyQualifiedName": "rendyplayground@gmail.com",
- "Type": "user",
- "Metadata": {
- "email": "rendyplayground@gmail.com",
- "role": "user",
- "team_domain": "",
- "team_name": "",
- "username": "rendyplayground"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "usage_data:view",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "rendy",
- "FullyQualifiedName": "rendyplayground@gmail.com",
- "Type": "user",
- "Metadata": {
- "email": "rendyplayground@gmail.com",
- "role": "user",
- "team_domain": "",
- "team_name": "",
- "username": "rendyplayground"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "team_workspaces:create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "rendy",
- "FullyQualifiedName": "rendyplayground@gmail.com",
- "Type": "user",
- "Metadata": {
- "email": "rendyplayground@gmail.com",
- "role": "user",
- "team_domain": "",
- "team_name": "",
- "username": "rendyplayground"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "team_workspaces:view",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": [
- {
- "Name": "My Workspace",
- "FullyQualifiedName": "4d06fc0c-6402-4a26-857d-80787b10eabf",
- "Type": "workspace",
- "Metadata": {
- "id": "4d06fc0c-6402-4a26-857d-80787b10eabf",
- "type": "personal",
- "visibility": "personal"
- },
- "Parent": null
- }
- ],
- "Metadata": null
- }
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/postman/permissions.go b/pkg/analyzer/analyzers/postman/permissions.go
deleted file mode 100644
index 2beb8b3cc995..000000000000
--- a/pkg/analyzer/analyzers/postman/permissions.go
+++ /dev/null
@@ -1,181 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package postman
-
-import "errors"
-
-type Permission int
-
-const (
- NoAccess Permission = iota
- UserAdd Permission = iota
- UserRemove Permission = iota
- TeamAdminManage Permission = iota
- TeamDevelopersManage Permission = iota
- SsoManage Permission = iota
- CustomDomainAdd Permission = iota
- CustomDomainEdit Permission = iota
- CustomDomainRemove Permission = iota
- AuditLogsView Permission = iota
- UsageDataView Permission = iota
- BillingMembersManage Permission = iota
- PaymentManage Permission = iota
- PlanUpdate Permission = iota
- TeamWorkspacesView Permission = iota
- TeamWorkspacesCreate Permission = iota
- TeamPublicProfileEnable Permission = iota
- TeamPrivateApiNetworkManage Permission = iota
- ParternerWorkspaceView Permission = iota
- ParternerWorkspaceManage Permission = iota
- ParternerWorkspaceVisibilityManage Permission = iota
- PartnersManage Permission = iota
- FlowAdd Permission = iota
- FlowEdit Permission = iota
- FlowRun Permission = iota
- FlowPublish Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- UserAdd: "user:add",
- UserRemove: "user:remove",
- TeamAdminManage: "team_admin:manage",
- TeamDevelopersManage: "team_developers:manage",
- SsoManage: "sso:manage",
- CustomDomainAdd: "custom_domain:add",
- CustomDomainEdit: "custom_domain:edit",
- CustomDomainRemove: "custom_domain:remove",
- AuditLogsView: "audit_logs:view",
- UsageDataView: "usage_data:view",
- BillingMembersManage: "billing_members:manage",
- PaymentManage: "payment:manage",
- PlanUpdate: "plan:update",
- TeamWorkspacesView: "team_workspaces:view",
- TeamWorkspacesCreate: "team_workspaces:create",
- TeamPublicProfileEnable: "team_public_profile:enable",
- TeamPrivateApiNetworkManage: "team_private_api_network:manage",
- ParternerWorkspaceView: "parterner_workspace:view",
- ParternerWorkspaceManage: "parterner_workspace:manage",
- ParternerWorkspaceVisibilityManage: "parterner_workspace_visibility:manage",
- PartnersManage: "partners:manage",
- FlowAdd: "flow:add",
- FlowEdit: "flow:edit",
- FlowRun: "flow:run",
- FlowPublish: "flow:publish",
- }
-
- StringToPermission = map[string]Permission{
- "user:add": UserAdd,
- "user:remove": UserRemove,
- "team_admin:manage": TeamAdminManage,
- "team_developers:manage": TeamDevelopersManage,
- "sso:manage": SsoManage,
- "custom_domain:add": CustomDomainAdd,
- "custom_domain:edit": CustomDomainEdit,
- "custom_domain:remove": CustomDomainRemove,
- "audit_logs:view": AuditLogsView,
- "usage_data:view": UsageDataView,
- "billing_members:manage": BillingMembersManage,
- "payment:manage": PaymentManage,
- "plan:update": PlanUpdate,
- "team_workspaces:view": TeamWorkspacesView,
- "team_workspaces:create": TeamWorkspacesCreate,
- "team_public_profile:enable": TeamPublicProfileEnable,
- "team_private_api_network:manage": TeamPrivateApiNetworkManage,
- "parterner_workspace:view": ParternerWorkspaceView,
- "parterner_workspace:manage": ParternerWorkspaceManage,
- "parterner_workspace_visibility:manage": ParternerWorkspaceVisibilityManage,
- "partners:manage": PartnersManage,
- "flow:add": FlowAdd,
- "flow:edit": FlowEdit,
- "flow:run": FlowRun,
- "flow:publish": FlowPublish,
- }
-
- PermissionIDs = map[Permission]int{
- UserAdd: 0,
- UserRemove: 1,
- TeamAdminManage: 2,
- TeamDevelopersManage: 3,
- SsoManage: 4,
- CustomDomainAdd: 5,
- CustomDomainEdit: 6,
- CustomDomainRemove: 7,
- AuditLogsView: 8,
- UsageDataView: 9,
- BillingMembersManage: 10,
- PaymentManage: 11,
- PlanUpdate: 12,
- TeamWorkspacesView: 13,
- TeamWorkspacesCreate: 14,
- TeamPublicProfileEnable: 15,
- TeamPrivateApiNetworkManage: 16,
- ParternerWorkspaceView: 17,
- ParternerWorkspaceManage: 18,
- ParternerWorkspaceVisibilityManage: 19,
- PartnersManage: 20,
- FlowAdd: 21,
- FlowEdit: 22,
- FlowRun: 23,
- FlowPublish: 24,
- }
-
- IdToPermission = map[int]Permission{
- 0: UserAdd,
- 1: UserRemove,
- 2: TeamAdminManage,
- 3: TeamDevelopersManage,
- 4: SsoManage,
- 5: CustomDomainAdd,
- 6: CustomDomainEdit,
- 7: CustomDomainRemove,
- 8: AuditLogsView,
- 9: UsageDataView,
- 10: BillingMembersManage,
- 11: PaymentManage,
- 12: PlanUpdate,
- 13: TeamWorkspacesView,
- 14: TeamWorkspacesCreate,
- 15: TeamPublicProfileEnable,
- 16: TeamPrivateApiNetworkManage,
- 17: ParternerWorkspaceView,
- 18: ParternerWorkspaceManage,
- 19: ParternerWorkspaceVisibilityManage,
- 20: PartnersManage,
- 21: FlowAdd,
- 22: FlowEdit,
- 23: FlowRun,
- 24: FlowPublish,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/postman/permissions.yaml b/pkg/analyzer/analyzers/postman/permissions.yaml
deleted file mode 100644
index fc87c2413600..000000000000
--- a/pkg/analyzer/analyzers/postman/permissions.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
-permissions:
-- user:add
-- user:remove
-- team_admin:manage
-- team_developers:manage
-- sso:manage
-- custom_domain:add
-- custom_domain:edit
-- custom_domain:remove
-- audit_logs:view
-- usage_data:view
-- billing_members:manage
-- payment:manage
-- plan:update
-- team_workspaces:view
-- team_workspaces:create
-- team_public_profile:enable
-- team_private_api_network:manage
-- parterner_workspace:view
-- parterner_workspace:manage
-- parterner_workspace_visibility:manage
-- partners:manage
-- flow:add
-- flow:edit
-- flow:run
-- flow:publish
-
diff --git a/pkg/analyzer/analyzers/postman/postman.go b/pkg/analyzer/analyzers/postman/postman.go
deleted file mode 100644
index 0f1b75dd139f..000000000000
--- a/pkg/analyzer/analyzers/postman/postman.go
+++ /dev/null
@@ -1,269 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go postman
-package postman
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "os"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypePostman }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, fmt.Errorf("missing key in credInfo")
- }
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypePostman,
- Metadata: nil,
- UnboundedResources: []analyzers.Resource{},
- }
-
- resource := analyzers.Resource{
- Name: info.User.User.FullName,
- FullyQualifiedName: info.User.User.Email,
- Type: "user",
- Metadata: map[string]any{
- "role": strings.Join(info.User.User.Roles, ","),
- "username": info.User.User.Username,
- "email": info.User.User.Email,
- "team_name": info.User.User.TeamName,
- "team_domain": info.User.User.TeamDomain,
- },
- }
-
- permissions := bakePermissions(info.User.User.Roles)
-
- // bind all permissions with resources
- result.Bindings = analyzers.BindAllPermissions(resource, permissions...)
-
- for _, workspace := range info.Workspace.Workspaces {
- result.UnboundedResources = append(result.UnboundedResources, analyzers.Resource{
- Name: workspace.Name,
- FullyQualifiedName: workspace.ID,
- Type: "workspace",
- Metadata: map[string]any{
- "id": workspace.ID,
- "type": workspace.Type,
- "visibility": workspace.Visibility,
- },
- })
- }
-
- return &result
-}
-
-func bakePermissions(roles []string) []analyzers.Permission {
- permissionMap := map[Permission]struct{}{}
-
- for _, role := range roles {
- permissions, ok := rolePermission[role]
- if !ok {
- continue
- }
- for _, permission := range permissions {
- permissionMap[permission] = struct{}{}
- }
- }
-
- permissions := make([]analyzers.Permission, 0, len(permissionMap))
- for perm := range permissionMap {
- permStr, err := perm.ToString()
- if err != nil {
- continue
- }
- permissions = append(permissions, analyzers.Permission{
- Value: permStr,
- Parent: nil,
- })
- }
-
- return permissions
-}
-
-type UserInfoJSON struct {
- User struct {
- Username string `json:"username"`
- Email string `json:"email"`
- FullName string `json:"fullName"`
- Roles []string `json:"roles"`
- TeamName string `json:"teamName"`
- TeamDomain string `json:"teamDomain"`
- } `json:"user"`
-}
-
-type WorkspaceJSON struct {
- Workspaces []struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"`
- Visibility string `json:"visibility"`
- } `json:"workspaces"`
-}
-
-func getUserInfo(cfg *config.Config, key string) (UserInfoJSON, error) {
- var me UserInfoJSON
-
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", "https://api.getpostman.com/me", nil)
- if err != nil {
- return me, err
- }
-
- req.Header.Add("X-API-Key", key)
-
- // send request
- resp, err := client.Do(req)
- if err != nil {
- return me, err
- }
-
- // read response
- defer resp.Body.Close()
-
- // if status code is 200, decode response
- if resp.StatusCode == 200 {
- err = json.NewDecoder(resp.Body).Decode(&me)
- }
- return me, err
-}
-
-func getWorkspaces(cfg *config.Config, key string) (WorkspaceJSON, error) {
- var workspaces WorkspaceJSON
-
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", "https://api.getpostman.com/workspaces", nil)
- if err != nil {
- return workspaces, err
- }
-
- req.Header.Add("X-API-Key", key)
-
- // send request
- resp, err := client.Do(req)
- if err != nil {
- return workspaces, err
- }
-
- // read response
- defer resp.Body.Close()
-
- // if status code is 200, decode response
- if resp.StatusCode == 200 {
- err = json.NewDecoder(resp.Body).Decode(&workspaces)
- }
- return workspaces, err
-}
-
-type SecretInfo struct {
- User UserInfoJSON
- Workspace WorkspaceJSON
- WorkspaceError error
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- // ToDo: Add in logging
- if cfg.LoggingEnabled {
- color.Red("[x] Logging is not supported for this analyzer.")
- return
- }
-
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
-
- color.Green("[!] Valid Postman API Key")
- printUserInfo(info.User)
-
- if info.WorkspaceError != nil {
- color.Red("[x] Error Fetching Workspaces: %s", info.WorkspaceError.Error())
- } else if len(info.Workspace.Workspaces) == 0 {
- color.Red("[x] No Workspaces Found")
- } else {
- printWorkspaces(info.Workspace)
- }
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- // validate key & get user info
-
- me, err := getUserInfo(cfg, key)
- if err != nil {
- return nil, err
- }
-
- if me.User.Username == "" {
- return nil, fmt.Errorf("Invalid Postman API Key")
- }
-
- // get workspaces, if there is error user with empty workspaces will be returned
- workspaces, err := getWorkspaces(cfg, key)
-
- return &SecretInfo{
- User: me,
- Workspace: workspaces,
- WorkspaceError: err,
- }, nil
-}
-
-func printUserInfo(me UserInfoJSON) {
-
- color.Yellow("\n[i] User Information")
- color.Green("Username: " + me.User.Username)
- color.Green("Email: " + me.User.Email)
- color.Green("Full Name: " + me.User.FullName)
-
- color.Yellow("\n[i] Team Information")
- color.Green("Name: " + me.User.TeamName)
- color.Green("Domain: https://" + me.User.TeamDomain + ".postman.co")
-
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Scope", "Permissions"})
-
- for _, role := range me.User.Roles {
- t.AppendRow([]interface{}{color.GreenString(role), color.GreenString(roleDescriptions[role])})
- }
- t.Render()
- fmt.Println("Reference: https://learning.postman.com/docs/collaborating-in-postman/roles-and-permissions/#team-roles")
-}
-
-func printWorkspaces(workspaces WorkspaceJSON) {
- color.Yellow("[i] Accessible Workspaces")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Workspace Name", "Type", "Visibility", "Link"})
- for _, workspace := range workspaces.Workspaces {
- t.AppendRow([]interface{}{color.GreenString(workspace.Name), color.GreenString(workspace.Type), color.GreenString(workspace.Visibility), color.GreenString("https://go.postman.co/workspaces/" + workspace.ID)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/postman/postman_test.go b/pkg/analyzer/analyzers/postman/postman_test.go
deleted file mode 100644
index 863826bd21ee..000000000000
--- a/pkg/analyzer/analyzers/postman/postman_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package postman
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid Postman key",
- key: testSecrets.MustGetField("POSTMAN_TOKEN"),
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/postman/scopes.go b/pkg/analyzer/analyzers/postman/scopes.go
deleted file mode 100644
index 46e8ca4f8c0b..000000000000
--- a/pkg/analyzer/analyzers/postman/scopes.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package postman
-
-var roleDescriptions = map[string]string{
- "super-admin": "(Enterprise Only) Manages everything within a team, including team settings, members, roles, and resources. This role can view and manage all elements in public, team, private, and personal workspaces. Super Admins can perform all actions that other roles can perform.",
- "admin": "Manages team members and team settings. Can also view monitor metadata and run, pause, and resume monitors.",
- "billing": "Manages team plan and payments. Billing roles can be granted by a Super Admin, Team Admin, or by a fellow team member with a Billing role.",
- "user": "Has access to all team resources and workspaces.",
- "community-manager": "(Pro & Enterprise Only) Manages the public visibility of workspaces and team profile.",
- "partner-manager": "(Internal, Enterprise plans only) - Manages all Partner Workspaces within an organization. Controls Partner Workspace settings and visibility, and can send invites to partners.",
- "partner": "(External, Professional and Enterprise plans only) - All partners are automatically granted the Partner role at the team level. Partners can only access the Partner Workspaces they've been invited to.",
- "guest": "Views collections and sends requests in collections that have been shared with them. This role can't be directly assigned to a user.",
- "flow-editor": "(Basic and Professional plans only) - Can create, edit, run, and publish Postman Flows.",
-}
-
-var rolePermission = map[string][]Permission{
- "super-admin": {
- UserAdd,
- UserRemove,
- TeamAdminManage,
- TeamDevelopersManage,
- SsoManage,
- CustomDomainAdd,
- CustomDomainEdit,
- CustomDomainRemove,
- AuditLogsView,
- UsageDataView,
- BillingMembersManage,
- PaymentManage,
- PlanUpdate,
- TeamWorkspacesView,
- TeamWorkspacesCreate,
- TeamPublicProfileEnable,
- TeamPrivateApiNetworkManage,
- PartnersManage,
- ParternerWorkspaceManage,
- ParternerWorkspaceView,
- ParternerWorkspaceVisibilityManage,
- FlowAdd,
- FlowEdit,
- FlowRun,
- FlowPublish,
- },
- "admin": {
- UserAdd,
- UserRemove,
- TeamAdminManage,
- TeamDevelopersManage,
- SsoManage,
- CustomDomainAdd,
- CustomDomainEdit,
- CustomDomainRemove,
- AuditLogsView,
- UsageDataView,
- BillingMembersManage,
- TeamPublicProfileEnable,
- PartnersManage,
- ParternerWorkspaceManage,
- ParternerWorkspaceView,
- ParternerWorkspaceVisibilityManage,
- FlowAdd,
- FlowEdit,
- FlowRun,
- FlowPublish,
- },
- "billing": {
- UsageDataView,
- BillingMembersManage,
- PaymentManage,
- PlanUpdate,
- },
- "user": {
- UsageDataView,
- TeamWorkspacesCreate,
- TeamWorkspacesView,
- },
- "community-manager": {
- CustomDomainAdd,
- CustomDomainEdit,
- AuditLogsView,
- UsageDataView,
- TeamWorkspacesView,
- TeamWorkspacesCreate,
- TeamPublicProfileEnable,
- },
- "partner-manager": {
- PartnersManage,
- ParternerWorkspaceManage,
- ParternerWorkspaceView,
- ParternerWorkspaceVisibilityManage,
- },
- "partner": {
- ParternerWorkspaceView,
- },
- "guest": {
- TeamWorkspacesView,
- },
- "flow-editor": {
- FlowAdd,
- FlowEdit,
- FlowRun,
- FlowPublish,
- },
-}
diff --git a/pkg/analyzer/analyzers/privatekey/expected_output.json b/pkg/analyzer/analyzers/privatekey/expected_output.json
deleted file mode 100644
index 56d005263737..000000000000
--- a/pkg/analyzer/analyzers/privatekey/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":21,"Bindings":[],"UnboundedResources":[{"Name":"*.gruponu3.com","FullyQualifiedName":"/*.gruponu3.com","Type":"certificate","Metadata":null,"Parent":null},{"Name":"techautm.in","FullyQualifiedName":"/techautm.in","Type":"certificate","Metadata":null,"Parent":null}],"Metadata":null}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/privatekey/permissions.go b/pkg/analyzer/analyzers/privatekey/permissions.go
deleted file mode 100644
index d2fbaa045719..000000000000
--- a/pkg/analyzer/analyzers/privatekey/permissions.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package privatekey
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Digitalsignature Permission = iota
- Nonrepudiation Permission = iota
- Keyencipherment Permission = iota
- Dataencipherment Permission = iota
- Keyagreement Permission = iota
- Certificatesigning Permission = iota
- Crlsigning Permission = iota
- Encipheronly Permission = iota
- Decipheronly Permission = iota
- Serverauth Permission = iota
- Clientauth Permission = iota
- Codesigning Permission = iota
- Emailprotection Permission = iota
- Timestamping Permission = iota
- Ocspsigning Permission = iota
- Clone Permission = iota
- Push Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Digitalsignature: "DigitalSignature",
- Nonrepudiation: "NonRepudiation",
- Keyencipherment: "KeyEncipherment",
- Dataencipherment: "DataEncipherment",
- Keyagreement: "KeyAgreement",
- Certificatesigning: "CertificateSigning",
- Crlsigning: "CRLSigning",
- Encipheronly: "EncipherOnly",
- Decipheronly: "DecipherOnly",
- Serverauth: "ServerAuth",
- Clientauth: "ClientAuth",
- Codesigning: "CodeSigning",
- Emailprotection: "EmailProtection",
- Timestamping: "TimeStamping",
- Ocspsigning: "OCSPSigning",
- Clone: "Clone",
- Push: "Push",
- }
-
- StringToPermission = map[string]Permission{
- "DigitalSignature": Digitalsignature,
- "NonRepudiation": Nonrepudiation,
- "KeyEncipherment": Keyencipherment,
- "DataEncipherment": Dataencipherment,
- "KeyAgreement": Keyagreement,
- "CertificateSigning": Certificatesigning,
- "CRLSigning": Crlsigning,
- "EncipherOnly": Encipheronly,
- "DecipherOnly": Decipheronly,
- "ServerAuth": Serverauth,
- "ClientAuth": Clientauth,
- "CodeSigning": Codesigning,
- "EmailProtection": Emailprotection,
- "TimeStamping": Timestamping,
- "OCSPSigning": Ocspsigning,
- "Clone": Clone,
- "Push": Push,
- }
-
- PermissionIDs = map[Permission]int{
- Digitalsignature: 1,
- Nonrepudiation: 2,
- Keyencipherment: 3,
- Dataencipherment: 4,
- Keyagreement: 5,
- Certificatesigning: 6,
- Crlsigning: 7,
- Encipheronly: 8,
- Decipheronly: 9,
- Serverauth: 10,
- Clientauth: 11,
- Codesigning: 12,
- Emailprotection: 13,
- Timestamping: 14,
- Ocspsigning: 15,
- Clone: 16,
- Push: 17,
- }
-
- IdToPermission = map[int]Permission{
- 1: Digitalsignature,
- 2: Nonrepudiation,
- 3: Keyencipherment,
- 4: Dataencipherment,
- 5: Keyagreement,
- 6: Certificatesigning,
- 7: Crlsigning,
- 8: Encipheronly,
- 9: Decipheronly,
- 10: Serverauth,
- 11: Clientauth,
- 12: Codesigning,
- 13: Emailprotection,
- 14: Timestamping,
- 15: Ocspsigning,
- 16: Clone,
- 17: Push,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/privatekey/permissions.yaml b/pkg/analyzer/analyzers/privatekey/permissions.yaml
deleted file mode 100644
index 583f61a37471..000000000000
--- a/pkg/analyzer/analyzers/privatekey/permissions.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
-permissions:
-# TLS:
-# KeyUsuage: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3
-# ExtendedKeyUsage: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12
-- DigitalSignature
-- NonRepudiation
-- KeyEncipherment
-- DataEncipherment
-- KeyAgreement
-- CertificateSigning
-- CRLSigning
-- EncipherOnly
-- DecipherOnly
-- ServerAuth
-- ClientAuth
-- CodeSigning
-- EmailProtection
-- TimeStamping
-- OCSPSigning
-
-# Github/Gitlab
-- Clone
-- Push
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/privatekey/privatekey.go b/pkg/analyzer/analyzers/privatekey/privatekey.go
deleted file mode 100644
index c270a5ce9006..000000000000
--- a/pkg/analyzer/analyzers/privatekey/privatekey.go
+++ /dev/null
@@ -1,330 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go privatekey
-
-package privatekey
-
-import (
- "errors"
- "fmt"
- "os"
- "regexp"
- "strings"
- "sync"
- "time"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/privatekey"
- "golang.org/x/crypto/ssh"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypePrivateKey }
-
-func (a Analyzer) Analyze(ctx context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- // token will be already normalized by the time it reaches here
- token, ok := credInfo["token"]
- if !ok {
- return nil, errors.New("token not found in credInfo")
- }
-
- info, err := AnalyzePermissions(ctx, a.Cfg, token)
- if err != nil {
- return nil, err
- }
-
- return secretInfoToAnalyzerResult(info), nil
-}
-
-type SecretInfo struct {
- TLSCertificateResult *privatekey.DriftwoodResult
- GithubUsername *string
- GitlabUsername *string
-}
-
-func AnalyzePermissions(ctx context.Context, cfg *config.Config, token string) (*SecretInfo, error) {
-
- var (
- wg sync.WaitGroup
- parsedKey any
- err error
- analyzerErrors = privatekey.NewVerificationErrors(3)
- info = &SecretInfo{}
- )
-
- parsedKey, err = ssh.ParseRawPrivateKey([]byte(token))
- if err != nil && strings.Contains(err.Error(), "private key is passphrase protected") {
- // key is password protected
- parsedKey, _, err = privatekey.Crack([]byte(token))
- if err != nil {
- return nil, err
- }
- } else if err != nil {
- return nil, err
- }
-
- fingerprint, err := privatekey.FingerprintPEMKey(parsedKey)
- if err != nil {
- return nil, err
- }
-
- // Look up certificate information.
- wg.Add(1)
- go func() {
- defer wg.Done()
- data, err := analyzeFingerprint(ctx, fingerprint)
- if err != nil {
- analyzerErrors.Add(err)
- } else {
- info.TLSCertificateResult = data
- }
- }()
-
- // Test SSH key against github.com
- wg.Add(1)
- go func() {
- defer wg.Done()
- user, err := analyzeGithubUser(ctx, parsedKey)
- if err != nil {
- analyzerErrors.Add(err)
- } else if user != nil {
- info.GithubUsername = user
- }
- }()
-
- // Test SSH key against gitlab.com
- wg.Add(1)
- go func() {
- defer wg.Done()
- user, err := analyzeGitlabUser(ctx, parsedKey)
- if err != nil {
- analyzerErrors.Add(err)
- } else if user != nil {
- info.GitlabUsername = user
- }
- }()
- wg.Wait()
-
- if len(analyzerErrors.Errors) == 3 {
- return nil, fmt.Errorf("analyzer failures: %s", strings.Join(analyzerErrors.Errors, ", "))
- }
-
- return info, nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- if cfg.LoggingEnabled {
- color.Red("[x] Logging is not supported for this analyzer.")
- return
- }
-
- token := privatekey.Normalize(key)
- if len(token) < 64 {
- color.Red("[x] Error: Invalid Private Key")
- return
- }
-
- // key entered through command line may have spaces instead of newlines, replace them
- token = replaceSpacesWithNewlines(token)
-
- info, err := AnalyzePermissions(context.Background(), cfg, token)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
-
- color.Green("[!] Valid Private Key\n\n")
-
- if info.GithubUsername == nil && info.GitlabUsername == nil && info.TLSCertificateResult == nil {
- color.Yellow("[i] Insufficient information returned from fingerprint analysis. No permissions found.")
- return
- }
-
- if info.GithubUsername != nil {
- color.Yellow("[i] GitHub Details:")
- printUserInfo(*info.GithubUsername)
- }
- if info.GitlabUsername != nil {
- color.Yellow("[i] GitLab Details:")
- printUserInfo(*info.GitlabUsername)
- }
- if info.TLSCertificateResult != nil {
- printTLSCertificateResult(info.TLSCertificateResult)
- }
-
-}
-
-func printUserInfo(username string) {
- color.Yellow("[i] Username: %s", username)
- color.Yellow("[i] Permissions: %s\n\n", color.GreenString("Clone/Push"))
-}
-
-func printTLSCertificateResult(result *privatekey.DriftwoodResult) {
- color.Yellow("[i] TLS Certificate Details:")
- fmt.Print("\n")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(
- table.Row{"Subject Key ID", "Subject Name", "Subject Organization", "Permissions", "Expiration Date", "Domains"})
- green := color.New(color.FgGreen).SprintFunc()
- for _, certificateResult := range result.CertificateResults {
- t.AppendRow([]interface{}{
- green(certificateResult.SubjectKeyID),
- green(certificateResult.SubjectName),
- green(strings.Join(certificateResult.SubjectOrganization, ", ")),
- green(strings.Join(append(certificateResult.KeyUsages, certificateResult.ExtendedKeyUsages...), ", ")),
- green(certificateResult.ExpirationTimestamp.Format(time.RFC3339)),
- green(strings.Join(certificateResult.Domains, ", ")),
- })
- }
- t.Render()
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypePrivateKey,
- Metadata: nil,
- Bindings: []analyzers.Binding{},
- UnboundedResources: []analyzers.Resource{},
- }
-
- if info.TLSCertificateResult != nil {
- bounded, unbounded := bakeTLSResources(info.TLSCertificateResult)
- result.Bindings = append(result.Bindings, bounded...)
- result.UnboundedResources = append(result.UnboundedResources, unbounded...)
- }
-
- if info.GithubUsername != nil {
- result.Bindings = append(result.Bindings, bakeGithubResources(info.GithubUsername)...)
- }
-
- if info.GitlabUsername != nil {
- result.Bindings = append(result.Bindings, bakeGitlabResources(info.GitlabUsername)...)
- }
-
- return &result
-}
-
-func bakeGithubResources(username *string) []analyzers.Binding {
- resource := &analyzers.Resource{
- Name: *username,
- FullyQualifiedName: fmt.Sprintf("github.com/user/%s", *username),
- Type: "user", // always user ???
- }
-
- permissions := []analyzers.Permission{
- {Value: PermissionStrings[Clone], Parent: nil},
- {Value: PermissionStrings[Push], Parent: nil},
- }
-
- return analyzers.BindAllPermissions(*resource, permissions...)
-}
-
-func bakeGitlabResources(username *string) []analyzers.Binding {
- resource := &analyzers.Resource{
- Name: *username,
- FullyQualifiedName: fmt.Sprintf("gitlab.com/user/%s", *username),
- Type: "user", // always user ???
- }
-
- permissions := []analyzers.Permission{
- {Value: PermissionStrings[Clone], Parent: nil},
- {Value: PermissionStrings[Push], Parent: nil},
- }
-
- return analyzers.BindAllPermissions(*resource, permissions...)
-}
-
-func bakeTLSResources(result *privatekey.DriftwoodResult) ([]analyzers.Binding, []analyzers.Resource) {
-
- unboundedResources := make([]analyzers.Resource, 0, len(result.CertificateResults))
- boundedResources := make([]analyzers.Binding, 0, len(result.CertificateResults))
-
- // iterate result.CertificateResults
- for _, cert := range result.CertificateResults {
- if cert.SubjectName == "" && cert.SubjectKeyID == "" {
- continue
- }
- resource := &analyzers.Resource{
- Name: cert.SubjectName,
- FullyQualifiedName: fmt.Sprintf("%s/%s", cert.SubjectKeyID, cert.SubjectName),
- Type: "certificate",
- }
- certPermissions := append(cert.KeyUsages, cert.ExtendedKeyUsages...)
- permissions := make([]analyzers.Permission, 0, len(certPermissions))
- for _, perm := range certPermissions {
- perm, ok := StringToPermission[perm]
- if !ok {
- continue
- }
- permissions = append(permissions, analyzers.Permission{
- Value: PermissionStrings[perm],
- Parent: nil,
- })
- }
-
- if len(permissions) > 0 {
- // bind all permissions with resources
- boundedResources = append(boundedResources, analyzers.BindAllPermissions(*resource, permissions...)...)
- } else {
- unboundedResources = append(unboundedResources, *resource)
- }
-
- }
-
- return boundedResources, unboundedResources
-}
-
-func analyzeFingerprint(ctx context.Context, fingerprint string) (*privatekey.DriftwoodResult, error) {
-
- result, err := privatekey.LookupFingerprint(ctx, fingerprint)
- if err != nil {
- return nil, err
- }
- if len(result.CertificateResults) == 0 {
- return nil, nil
- }
- return result, nil
-}
-
-func analyzeGithubUser(ctx context.Context, parsedKey any) (*string, error) {
- return privatekey.VerifyGitHubUser(ctx, parsedKey)
-}
-
-func analyzeGitlabUser(ctx context.Context, parsedKey any) (*string, error) {
- return privatekey.VerifyGitLabUser(ctx, parsedKey)
-}
-
-// replaceSpacesWithNewlines extracts the base64 part, replaces spaces with newlines if needed, and reconstructs the key.
-func replaceSpacesWithNewlines(privateKey string) string {
- // Regex pattern to extract the key content
- re := regexp.MustCompile(`(?i)(-----\s*BEGIN[ A-Z0-9_-]*PRIVATE KEY\s*-----)\s*([\s\S]*?)\s*(-----\s*END[ A-Z0-9_-]*PRIVATE KEY\s*-----)`)
-
- // Find matches
- matches := re.FindStringSubmatch(privateKey)
- if len(matches) != 4 {
- // no need to process
- return privateKey
- }
-
- header := matches[1] // BEGIN line
- base64Part := matches[2] // Base64 content
- footer := matches[3] // END line
-
- // Replace spaces with newlines
- formattedBase64 := strings.ReplaceAll(base64Part, " ", "\n")
-
- // Reconstruct the private key
- return fmt.Sprintf("%s\n%s\n%s", header, formattedBase64, footer)
-}
diff --git a/pkg/analyzer/analyzers/privatekey/privatekey_test.go b/pkg/analyzer/analyzers/privatekey/privatekey_test.go
deleted file mode 100644
index 7849579e8684..000000000000
--- a/pkg/analyzer/analyzers/privatekey/privatekey_test.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package privatekey
-
-import (
- _ "embed"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- privateKey := testSecrets.MustGetField("PRIVATEKEY_TLS")
-
- tests := []struct {
- name string
- key string
- storeUrl string
- want string
- wantErr bool
- }{
- {
- name: "valid TLS key",
- key: privateKey,
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/sendgrid/permissions.go b/pkg/analyzer/analyzers/sendgrid/permissions.go
deleted file mode 100644
index 996c0b0ed876..000000000000
--- a/pkg/analyzer/analyzers/sendgrid/permissions.go
+++ /dev/null
@@ -1,991 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package sendgrid
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- AccessSettingsActivityRead Permission = iota
- AccessSettingsWhitelistCreate Permission = iota
- AccessSettingsWhitelistDelete Permission = iota
- AccessSettingsWhitelistRead Permission = iota
- AccessSettingsWhitelistUpdate Permission = iota
- AlertsCreate Permission = iota
- AlertsDelete Permission = iota
- AlertsRead Permission = iota
- AlertsUpdate Permission = iota
- ApiKeysCreate Permission = iota
- ApiKeysDelete Permission = iota
- ApiKeysRead Permission = iota
- ApiKeysUpdate Permission = iota
- AsmGroupsCreate Permission = iota
- AsmGroupsDelete Permission = iota
- AsmGroupsRead Permission = iota
- AsmGroupsUpdate Permission = iota
- BillingCreate Permission = iota
- BillingDelete Permission = iota
- BillingRead Permission = iota
- BillingUpdate Permission = iota
- BrowsersStatsRead Permission = iota
- CategoriesCreate Permission = iota
- CategoriesDelete Permission = iota
- CategoriesRead Permission = iota
- CategoriesStatsRead Permission = iota
- CategoriesStatsSumsRead Permission = iota
- CategoriesUpdate Permission = iota
- ClientsDesktopStatsRead Permission = iota
- ClientsPhoneStatsRead Permission = iota
- ClientsStatsRead Permission = iota
- ClientsTabletStatsRead Permission = iota
- ClientsWebmailStatsRead Permission = iota
- DevicesStatsRead Permission = iota
- EmailActivityRead Permission = iota
- GeoStatsRead Permission = iota
- IpsAssignedRead Permission = iota
- IpsPoolsCreate Permission = iota
- IpsPoolsDelete Permission = iota
- IpsPoolsIpsCreate Permission = iota
- IpsPoolsIpsDelete Permission = iota
- IpsPoolsIpsRead Permission = iota
- IpsPoolsIpsUpdate Permission = iota
- IpsPoolsRead Permission = iota
- IpsPoolsUpdate Permission = iota
- IpsRead Permission = iota
- IpsWarmupCreate Permission = iota
- IpsWarmupDelete Permission = iota
- IpsWarmupRead Permission = iota
- IpsWarmupUpdate Permission = iota
- MailSettingsAddressWhitelistRead Permission = iota
- MailSettingsAddressWhitelistUpdate Permission = iota
- MailSettingsBouncePurgeRead Permission = iota
- MailSettingsBouncePurgeUpdate Permission = iota
- MailSettingsFooterRead Permission = iota
- MailSettingsFooterUpdate Permission = iota
- MailSettingsForwardBounceRead Permission = iota
- MailSettingsForwardBounceUpdate Permission = iota
- MailSettingsForwardSpamRead Permission = iota
- MailSettingsForwardSpamUpdate Permission = iota
- MailSettingsPlainContentRead Permission = iota
- MailSettingsPlainContentUpdate Permission = iota
- MailSettingsRead Permission = iota
- MailSettingsTemplateRead Permission = iota
- MailSettingsTemplateUpdate Permission = iota
- MailBatchCreate Permission = iota
- MailBatchDelete Permission = iota
- MailBatchRead Permission = iota
- MailBatchUpdate Permission = iota
- MailSend Permission = iota
- MailboxProvidersStatsRead Permission = iota
- MarketingCampaignsCreate Permission = iota
- MarketingCampaignsDelete Permission = iota
- MarketingCampaignsRead Permission = iota
- MarketingCampaignsUpdate Permission = iota
- PartnerSettingsNewRelicRead Permission = iota
- PartnerSettingsNewRelicUpdate Permission = iota
- PartnerSettingsRead Permission = iota
- StatsGlobalRead Permission = iota
- StatsRead Permission = iota
- SubusersCreate Permission = iota
- SubusersCreditsCreate Permission = iota
- SubusersCreditsDelete Permission = iota
- SubusersCreditsRead Permission = iota
- SubusersCreditsRemainingCreate Permission = iota
- SubusersCreditsRemainingDelete Permission = iota
- SubusersCreditsRemainingRead Permission = iota
- SubusersCreditsRemainingUpdate Permission = iota
- SubusersCreditsUpdate Permission = iota
- SubusersDelete Permission = iota
- SubusersMonitorCreate Permission = iota
- SubusersMonitorDelete Permission = iota
- SubusersMonitorRead Permission = iota
- SubusersMonitorUpdate Permission = iota
- SubusersRead Permission = iota
- SubusersReputationsRead Permission = iota
- SubusersStatsMonthlyRead Permission = iota
- SubusersStatsRead Permission = iota
- SubusersStatsSumsRead Permission = iota
- SubusersSummaryRead Permission = iota
- SubusersUpdate Permission = iota
- SuppressionBlocksCreate Permission = iota
- SuppressionBlocksDelete Permission = iota
- SuppressionBlocksRead Permission = iota
- SuppressionBlocksUpdate Permission = iota
- SuppressionBouncesCreate Permission = iota
- SuppressionBouncesDelete Permission = iota
- SuppressionBouncesRead Permission = iota
- SuppressionBouncesUpdate Permission = iota
- SuppressionCreate Permission = iota
- SuppressionDelete Permission = iota
- SuppressionInvalidEmailsCreate Permission = iota
- SuppressionInvalidEmailsDelete Permission = iota
- SuppressionInvalidEmailsRead Permission = iota
- SuppressionInvalidEmailsUpdate Permission = iota
- SuppressionRead Permission = iota
- SuppressionSpamReportsCreate Permission = iota
- SuppressionSpamReportsDelete Permission = iota
- SuppressionSpamReportsRead Permission = iota
- SuppressionSpamReportsUpdate Permission = iota
- SuppressionUnsubscribesCreate Permission = iota
- SuppressionUnsubscribesDelete Permission = iota
- SuppressionUnsubscribesRead Permission = iota
- SuppressionUnsubscribesUpdate Permission = iota
- SuppressionUpdate Permission = iota
- TeammatesCreate Permission = iota
- TeammatesRead Permission = iota
- TeammatesUpdate Permission = iota
- TeammatesDelete Permission = iota
- TemplatesCreate Permission = iota
- TemplatesDelete Permission = iota
- TemplatesRead Permission = iota
- TemplatesUpdate Permission = iota
- TemplatesVersionsActivateCreate Permission = iota
- TemplatesVersionsActivateDelete Permission = iota
- TemplatesVersionsActivateRead Permission = iota
- TemplatesVersionsActivateUpdate Permission = iota
- TemplatesVersionsCreate Permission = iota
- TemplatesVersionsDelete Permission = iota
- TemplatesVersionsRead Permission = iota
- TemplatesVersionsUpdate Permission = iota
- TrackingSettingsClickRead Permission = iota
- TrackingSettingsClickUpdate Permission = iota
- TrackingSettingsGoogleAnalyticsRead Permission = iota
- TrackingSettingsGoogleAnalyticsUpdate Permission = iota
- TrackingSettingsOpenRead Permission = iota
- TrackingSettingsOpenUpdate Permission = iota
- TrackingSettingsRead Permission = iota
- TrackingSettingsSubscriptionRead Permission = iota
- TrackingSettingsSubscriptionUpdate Permission = iota
- UserAccountRead Permission = iota
- UserCreditsRead Permission = iota
- UserEmailCreate Permission = iota
- UserEmailDelete Permission = iota
- UserEmailRead Permission = iota
- UserEmailUpdate Permission = iota
- UserMultifactorAuthenticationCreate Permission = iota
- UserMultifactorAuthenticationDelete Permission = iota
- UserMultifactorAuthenticationRead Permission = iota
- UserMultifactorAuthenticationUpdate Permission = iota
- UserPasswordRead Permission = iota
- UserPasswordUpdate Permission = iota
- UserProfileRead Permission = iota
- UserProfileUpdate Permission = iota
- UserScheduledSendsCreate Permission = iota
- UserScheduledSendsDelete Permission = iota
- UserScheduledSendsRead Permission = iota
- UserScheduledSendsUpdate Permission = iota
- UserSettingsEnforcedTlsRead Permission = iota
- UserSettingsEnforcedTlsUpdate Permission = iota
- UserTimezoneRead Permission = iota
- UserUsernameRead Permission = iota
- UserUsernameUpdate Permission = iota
- UserWebhooksEventSettingsRead Permission = iota
- UserWebhooksEventSettingsUpdate Permission = iota
- UserWebhooksEventTestCreate Permission = iota
- UserWebhooksEventTestRead Permission = iota
- UserWebhooksEventTestUpdate Permission = iota
- UserWebhooksParseSettingsCreate Permission = iota
- UserWebhooksParseSettingsDelete Permission = iota
- UserWebhooksParseSettingsRead Permission = iota
- UserWebhooksParseSettingsUpdate Permission = iota
- UserWebhooksParseStatsRead Permission = iota
- WhitelabelCreate Permission = iota
- WhitelabelDelete Permission = iota
- WhitelabelRead Permission = iota
- WhitelabelUpdate Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- AccessSettingsActivityRead: "access_settings.activity.read",
- AccessSettingsWhitelistCreate: "access_settings.whitelist.create",
- AccessSettingsWhitelistDelete: "access_settings.whitelist.delete",
- AccessSettingsWhitelistRead: "access_settings.whitelist.read",
- AccessSettingsWhitelistUpdate: "access_settings.whitelist.update",
- AlertsCreate: "alerts.create",
- AlertsDelete: "alerts.delete",
- AlertsRead: "alerts.read",
- AlertsUpdate: "alerts.update",
- ApiKeysCreate: "api_keys.create",
- ApiKeysDelete: "api_keys.delete",
- ApiKeysRead: "api_keys.read",
- ApiKeysUpdate: "api_keys.update",
- AsmGroupsCreate: "asm.groups.create",
- AsmGroupsDelete: "asm.groups.delete",
- AsmGroupsRead: "asm.groups.read",
- AsmGroupsUpdate: "asm.groups.update",
- BillingCreate: "billing.create",
- BillingDelete: "billing.delete",
- BillingRead: "billing.read",
- BillingUpdate: "billing.update",
- BrowsersStatsRead: "browsers.stats.read",
- CategoriesCreate: "categories.create",
- CategoriesDelete: "categories.delete",
- CategoriesRead: "categories.read",
- CategoriesStatsRead: "categories.stats.read",
- CategoriesStatsSumsRead: "categories.stats.sums.read",
- CategoriesUpdate: "categories.update",
- ClientsDesktopStatsRead: "clients.desktop.stats.read",
- ClientsPhoneStatsRead: "clients.phone.stats.read",
- ClientsStatsRead: "clients.stats.read",
- ClientsTabletStatsRead: "clients.tablet.stats.read",
- ClientsWebmailStatsRead: "clients.webmail.stats.read",
- DevicesStatsRead: "devices.stats.read",
- EmailActivityRead: "email_activity.read",
- GeoStatsRead: "geo.stats.read",
- IpsAssignedRead: "ips.assigned.read",
- IpsPoolsCreate: "ips.pools.create",
- IpsPoolsDelete: "ips.pools.delete",
- IpsPoolsIpsCreate: "ips.pools.ips.create",
- IpsPoolsIpsDelete: "ips.pools.ips.delete",
- IpsPoolsIpsRead: "ips.pools.ips.read",
- IpsPoolsIpsUpdate: "ips.pools.ips.update",
- IpsPoolsRead: "ips.pools.read",
- IpsPoolsUpdate: "ips.pools.update",
- IpsRead: "ips.read",
- IpsWarmupCreate: "ips.warmup.create",
- IpsWarmupDelete: "ips.warmup.delete",
- IpsWarmupRead: "ips.warmup.read",
- IpsWarmupUpdate: "ips.warmup.update",
- MailSettingsAddressWhitelistRead: "mail_settings.address_whitelist.read",
- MailSettingsAddressWhitelistUpdate: "mail_settings.address_whitelist.update",
- MailSettingsBouncePurgeRead: "mail_settings.bounce_purge.read",
- MailSettingsBouncePurgeUpdate: "mail_settings.bounce_purge.update",
- MailSettingsFooterRead: "mail_settings.footer.read",
- MailSettingsFooterUpdate: "mail_settings.footer.update",
- MailSettingsForwardBounceRead: "mail_settings.forward_bounce.read",
- MailSettingsForwardBounceUpdate: "mail_settings.forward_bounce.update",
- MailSettingsForwardSpamRead: "mail_settings.forward_spam.read",
- MailSettingsForwardSpamUpdate: "mail_settings.forward_spam.update",
- MailSettingsPlainContentRead: "mail_settings.plain_content.read",
- MailSettingsPlainContentUpdate: "mail_settings.plain_content.update",
- MailSettingsRead: "mail_settings.read",
- MailSettingsTemplateRead: "mail_settings.template.read",
- MailSettingsTemplateUpdate: "mail_settings.template.update",
- MailBatchCreate: "mail.batch.create",
- MailBatchDelete: "mail.batch.delete",
- MailBatchRead: "mail.batch.read",
- MailBatchUpdate: "mail.batch.update",
- MailSend: "mail.send",
- MailboxProvidersStatsRead: "mailbox_providers.stats.read",
- MarketingCampaignsCreate: "marketing_campaigns.create",
- MarketingCampaignsDelete: "marketing_campaigns.delete",
- MarketingCampaignsRead: "marketing_campaigns.read",
- MarketingCampaignsUpdate: "marketing_campaigns.update",
- PartnerSettingsNewRelicRead: "partner_settings.new_relic.read",
- PartnerSettingsNewRelicUpdate: "partner_settings.new_relic.update",
- PartnerSettingsRead: "partner_settings.read",
- StatsGlobalRead: "stats.global.read",
- StatsRead: "stats.read",
- SubusersCreate: "subusers.create",
- SubusersCreditsCreate: "subusers.credits.create",
- SubusersCreditsDelete: "subusers.credits.delete",
- SubusersCreditsRead: "subusers.credits.read",
- SubusersCreditsRemainingCreate: "subusers.credits.remaining.create",
- SubusersCreditsRemainingDelete: "subusers.credits.remaining.delete",
- SubusersCreditsRemainingRead: "subusers.credits.remaining.read",
- SubusersCreditsRemainingUpdate: "subusers.credits.remaining.update",
- SubusersCreditsUpdate: "subusers.credits.update",
- SubusersDelete: "subusers.delete",
- SubusersMonitorCreate: "subusers.monitor.create",
- SubusersMonitorDelete: "subusers.monitor.delete",
- SubusersMonitorRead: "subusers.monitor.read",
- SubusersMonitorUpdate: "subusers.monitor.update",
- SubusersRead: "subusers.read",
- SubusersReputationsRead: "subusers.reputations.read",
- SubusersStatsMonthlyRead: "subusers.stats.monthly.read",
- SubusersStatsRead: "subusers.stats.read",
- SubusersStatsSumsRead: "subusers.stats.sums.read",
- SubusersSummaryRead: "subusers.summary.read",
- SubusersUpdate: "subusers.update",
- SuppressionBlocksCreate: "suppression.blocks.create",
- SuppressionBlocksDelete: "suppression.blocks.delete",
- SuppressionBlocksRead: "suppression.blocks.read",
- SuppressionBlocksUpdate: "suppression.blocks.update",
- SuppressionBouncesCreate: "suppression.bounces.create",
- SuppressionBouncesDelete: "suppression.bounces.delete",
- SuppressionBouncesRead: "suppression.bounces.read",
- SuppressionBouncesUpdate: "suppression.bounces.update",
- SuppressionCreate: "suppression.create",
- SuppressionDelete: "suppression.delete",
- SuppressionInvalidEmailsCreate: "suppression.invalid_emails.create",
- SuppressionInvalidEmailsDelete: "suppression.invalid_emails.delete",
- SuppressionInvalidEmailsRead: "suppression.invalid_emails.read",
- SuppressionInvalidEmailsUpdate: "suppression.invalid_emails.update",
- SuppressionRead: "suppression.read",
- SuppressionSpamReportsCreate: "suppression.spam_reports.create",
- SuppressionSpamReportsDelete: "suppression.spam_reports.delete",
- SuppressionSpamReportsRead: "suppression.spam_reports.read",
- SuppressionSpamReportsUpdate: "suppression.spam_reports.update",
- SuppressionUnsubscribesCreate: "suppression.unsubscribes.create",
- SuppressionUnsubscribesDelete: "suppression.unsubscribes.delete",
- SuppressionUnsubscribesRead: "suppression.unsubscribes.read",
- SuppressionUnsubscribesUpdate: "suppression.unsubscribes.update",
- SuppressionUpdate: "suppression.update",
- TeammatesCreate: "teammates.create",
- TeammatesRead: "teammates.read",
- TeammatesUpdate: "teammates.update",
- TeammatesDelete: "teammates.delete",
- TemplatesCreate: "templates.create",
- TemplatesDelete: "templates.delete",
- TemplatesRead: "templates.read",
- TemplatesUpdate: "templates.update",
- TemplatesVersionsActivateCreate: "templates.versions.activate.create",
- TemplatesVersionsActivateDelete: "templates.versions.activate.delete",
- TemplatesVersionsActivateRead: "templates.versions.activate.read",
- TemplatesVersionsActivateUpdate: "templates.versions.activate.update",
- TemplatesVersionsCreate: "templates.versions.create",
- TemplatesVersionsDelete: "templates.versions.delete",
- TemplatesVersionsRead: "templates.versions.read",
- TemplatesVersionsUpdate: "templates.versions.update",
- TrackingSettingsClickRead: "tracking_settings.click.read",
- TrackingSettingsClickUpdate: "tracking_settings.click.update",
- TrackingSettingsGoogleAnalyticsRead: "tracking_settings.google_analytics.read",
- TrackingSettingsGoogleAnalyticsUpdate: "tracking_settings.google_analytics.update",
- TrackingSettingsOpenRead: "tracking_settings.open.read",
- TrackingSettingsOpenUpdate: "tracking_settings.open.update",
- TrackingSettingsRead: "tracking_settings.read",
- TrackingSettingsSubscriptionRead: "tracking_settings.subscription.read",
- TrackingSettingsSubscriptionUpdate: "tracking_settings.subscription.update",
- UserAccountRead: "user.account.read",
- UserCreditsRead: "user.credits.read",
- UserEmailCreate: "user.email.create",
- UserEmailDelete: "user.email.delete",
- UserEmailRead: "user.email.read",
- UserEmailUpdate: "user.email.update",
- UserMultifactorAuthenticationCreate: "user.multifactor_authentication.create",
- UserMultifactorAuthenticationDelete: "user.multifactor_authentication.delete",
- UserMultifactorAuthenticationRead: "user.multifactor_authentication.read",
- UserMultifactorAuthenticationUpdate: "user.multifactor_authentication.update",
- UserPasswordRead: "user.password.read",
- UserPasswordUpdate: "user.password.update",
- UserProfileRead: "user.profile.read",
- UserProfileUpdate: "user.profile.update",
- UserScheduledSendsCreate: "user.scheduled_sends.create",
- UserScheduledSendsDelete: "user.scheduled_sends.delete",
- UserScheduledSendsRead: "user.scheduled_sends.read",
- UserScheduledSendsUpdate: "user.scheduled_sends.update",
- UserSettingsEnforcedTlsRead: "user.settings.enforced_tls.read",
- UserSettingsEnforcedTlsUpdate: "user.settings.enforced_tls.update",
- UserTimezoneRead: "user.timezone.read",
- UserUsernameRead: "user.username.read",
- UserUsernameUpdate: "user.username.update",
- UserWebhooksEventSettingsRead: "user.webhooks.event.settings.read",
- UserWebhooksEventSettingsUpdate: "user.webhooks.event.settings.update",
- UserWebhooksEventTestCreate: "user.webhooks.event.test.create",
- UserWebhooksEventTestRead: "user.webhooks.event.test.read",
- UserWebhooksEventTestUpdate: "user.webhooks.event.test.update",
- UserWebhooksParseSettingsCreate: "user.webhooks.parse.settings.create",
- UserWebhooksParseSettingsDelete: "user.webhooks.parse.settings.delete",
- UserWebhooksParseSettingsRead: "user.webhooks.parse.settings.read",
- UserWebhooksParseSettingsUpdate: "user.webhooks.parse.settings.update",
- UserWebhooksParseStatsRead: "user.webhooks.parse.stats.read",
- WhitelabelCreate: "whitelabel.create",
- WhitelabelDelete: "whitelabel.delete",
- WhitelabelRead: "whitelabel.read",
- WhitelabelUpdate: "whitelabel.update",
- }
-
- StringToPermission = map[string]Permission{
- "access_settings.activity.read": AccessSettingsActivityRead,
- "access_settings.whitelist.create": AccessSettingsWhitelistCreate,
- "access_settings.whitelist.delete": AccessSettingsWhitelistDelete,
- "access_settings.whitelist.read": AccessSettingsWhitelistRead,
- "access_settings.whitelist.update": AccessSettingsWhitelistUpdate,
- "alerts.create": AlertsCreate,
- "alerts.delete": AlertsDelete,
- "alerts.read": AlertsRead,
- "alerts.update": AlertsUpdate,
- "api_keys.create": ApiKeysCreate,
- "api_keys.delete": ApiKeysDelete,
- "api_keys.read": ApiKeysRead,
- "api_keys.update": ApiKeysUpdate,
- "asm.groups.create": AsmGroupsCreate,
- "asm.groups.delete": AsmGroupsDelete,
- "asm.groups.read": AsmGroupsRead,
- "asm.groups.update": AsmGroupsUpdate,
- "billing.create": BillingCreate,
- "billing.delete": BillingDelete,
- "billing.read": BillingRead,
- "billing.update": BillingUpdate,
- "browsers.stats.read": BrowsersStatsRead,
- "categories.create": CategoriesCreate,
- "categories.delete": CategoriesDelete,
- "categories.read": CategoriesRead,
- "categories.stats.read": CategoriesStatsRead,
- "categories.stats.sums.read": CategoriesStatsSumsRead,
- "categories.update": CategoriesUpdate,
- "clients.desktop.stats.read": ClientsDesktopStatsRead,
- "clients.phone.stats.read": ClientsPhoneStatsRead,
- "clients.stats.read": ClientsStatsRead,
- "clients.tablet.stats.read": ClientsTabletStatsRead,
- "clients.webmail.stats.read": ClientsWebmailStatsRead,
- "devices.stats.read": DevicesStatsRead,
- "email_activity.read": EmailActivityRead,
- "geo.stats.read": GeoStatsRead,
- "ips.assigned.read": IpsAssignedRead,
- "ips.pools.create": IpsPoolsCreate,
- "ips.pools.delete": IpsPoolsDelete,
- "ips.pools.ips.create": IpsPoolsIpsCreate,
- "ips.pools.ips.delete": IpsPoolsIpsDelete,
- "ips.pools.ips.read": IpsPoolsIpsRead,
- "ips.pools.ips.update": IpsPoolsIpsUpdate,
- "ips.pools.read": IpsPoolsRead,
- "ips.pools.update": IpsPoolsUpdate,
- "ips.read": IpsRead,
- "ips.warmup.create": IpsWarmupCreate,
- "ips.warmup.delete": IpsWarmupDelete,
- "ips.warmup.read": IpsWarmupRead,
- "ips.warmup.update": IpsWarmupUpdate,
- "mail_settings.address_whitelist.read": MailSettingsAddressWhitelistRead,
- "mail_settings.address_whitelist.update": MailSettingsAddressWhitelistUpdate,
- "mail_settings.bounce_purge.read": MailSettingsBouncePurgeRead,
- "mail_settings.bounce_purge.update": MailSettingsBouncePurgeUpdate,
- "mail_settings.footer.read": MailSettingsFooterRead,
- "mail_settings.footer.update": MailSettingsFooterUpdate,
- "mail_settings.forward_bounce.read": MailSettingsForwardBounceRead,
- "mail_settings.forward_bounce.update": MailSettingsForwardBounceUpdate,
- "mail_settings.forward_spam.read": MailSettingsForwardSpamRead,
- "mail_settings.forward_spam.update": MailSettingsForwardSpamUpdate,
- "mail_settings.plain_content.read": MailSettingsPlainContentRead,
- "mail_settings.plain_content.update": MailSettingsPlainContentUpdate,
- "mail_settings.read": MailSettingsRead,
- "mail_settings.template.read": MailSettingsTemplateRead,
- "mail_settings.template.update": MailSettingsTemplateUpdate,
- "mail.batch.create": MailBatchCreate,
- "mail.batch.delete": MailBatchDelete,
- "mail.batch.read": MailBatchRead,
- "mail.batch.update": MailBatchUpdate,
- "mail.send": MailSend,
- "mailbox_providers.stats.read": MailboxProvidersStatsRead,
- "marketing_campaigns.create": MarketingCampaignsCreate,
- "marketing_campaigns.delete": MarketingCampaignsDelete,
- "marketing_campaigns.read": MarketingCampaignsRead,
- "marketing_campaigns.update": MarketingCampaignsUpdate,
- "partner_settings.new_relic.read": PartnerSettingsNewRelicRead,
- "partner_settings.new_relic.update": PartnerSettingsNewRelicUpdate,
- "partner_settings.read": PartnerSettingsRead,
- "stats.global.read": StatsGlobalRead,
- "stats.read": StatsRead,
- "subusers.create": SubusersCreate,
- "subusers.credits.create": SubusersCreditsCreate,
- "subusers.credits.delete": SubusersCreditsDelete,
- "subusers.credits.read": SubusersCreditsRead,
- "subusers.credits.remaining.create": SubusersCreditsRemainingCreate,
- "subusers.credits.remaining.delete": SubusersCreditsRemainingDelete,
- "subusers.credits.remaining.read": SubusersCreditsRemainingRead,
- "subusers.credits.remaining.update": SubusersCreditsRemainingUpdate,
- "subusers.credits.update": SubusersCreditsUpdate,
- "subusers.delete": SubusersDelete,
- "subusers.monitor.create": SubusersMonitorCreate,
- "subusers.monitor.delete": SubusersMonitorDelete,
- "subusers.monitor.read": SubusersMonitorRead,
- "subusers.monitor.update": SubusersMonitorUpdate,
- "subusers.read": SubusersRead,
- "subusers.reputations.read": SubusersReputationsRead,
- "subusers.stats.monthly.read": SubusersStatsMonthlyRead,
- "subusers.stats.read": SubusersStatsRead,
- "subusers.stats.sums.read": SubusersStatsSumsRead,
- "subusers.summary.read": SubusersSummaryRead,
- "subusers.update": SubusersUpdate,
- "suppression.blocks.create": SuppressionBlocksCreate,
- "suppression.blocks.delete": SuppressionBlocksDelete,
- "suppression.blocks.read": SuppressionBlocksRead,
- "suppression.blocks.update": SuppressionBlocksUpdate,
- "suppression.bounces.create": SuppressionBouncesCreate,
- "suppression.bounces.delete": SuppressionBouncesDelete,
- "suppression.bounces.read": SuppressionBouncesRead,
- "suppression.bounces.update": SuppressionBouncesUpdate,
- "suppression.create": SuppressionCreate,
- "suppression.delete": SuppressionDelete,
- "suppression.invalid_emails.create": SuppressionInvalidEmailsCreate,
- "suppression.invalid_emails.delete": SuppressionInvalidEmailsDelete,
- "suppression.invalid_emails.read": SuppressionInvalidEmailsRead,
- "suppression.invalid_emails.update": SuppressionInvalidEmailsUpdate,
- "suppression.read": SuppressionRead,
- "suppression.spam_reports.create": SuppressionSpamReportsCreate,
- "suppression.spam_reports.delete": SuppressionSpamReportsDelete,
- "suppression.spam_reports.read": SuppressionSpamReportsRead,
- "suppression.spam_reports.update": SuppressionSpamReportsUpdate,
- "suppression.unsubscribes.create": SuppressionUnsubscribesCreate,
- "suppression.unsubscribes.delete": SuppressionUnsubscribesDelete,
- "suppression.unsubscribes.read": SuppressionUnsubscribesRead,
- "suppression.unsubscribes.update": SuppressionUnsubscribesUpdate,
- "suppression.update": SuppressionUpdate,
- "teammates.create": TeammatesCreate,
- "teammates.read": TeammatesRead,
- "teammates.update": TeammatesUpdate,
- "teammates.delete": TeammatesDelete,
- "templates.create": TemplatesCreate,
- "templates.delete": TemplatesDelete,
- "templates.read": TemplatesRead,
- "templates.update": TemplatesUpdate,
- "templates.versions.activate.create": TemplatesVersionsActivateCreate,
- "templates.versions.activate.delete": TemplatesVersionsActivateDelete,
- "templates.versions.activate.read": TemplatesVersionsActivateRead,
- "templates.versions.activate.update": TemplatesVersionsActivateUpdate,
- "templates.versions.create": TemplatesVersionsCreate,
- "templates.versions.delete": TemplatesVersionsDelete,
- "templates.versions.read": TemplatesVersionsRead,
- "templates.versions.update": TemplatesVersionsUpdate,
- "tracking_settings.click.read": TrackingSettingsClickRead,
- "tracking_settings.click.update": TrackingSettingsClickUpdate,
- "tracking_settings.google_analytics.read": TrackingSettingsGoogleAnalyticsRead,
- "tracking_settings.google_analytics.update": TrackingSettingsGoogleAnalyticsUpdate,
- "tracking_settings.open.read": TrackingSettingsOpenRead,
- "tracking_settings.open.update": TrackingSettingsOpenUpdate,
- "tracking_settings.read": TrackingSettingsRead,
- "tracking_settings.subscription.read": TrackingSettingsSubscriptionRead,
- "tracking_settings.subscription.update": TrackingSettingsSubscriptionUpdate,
- "user.account.read": UserAccountRead,
- "user.credits.read": UserCreditsRead,
- "user.email.create": UserEmailCreate,
- "user.email.delete": UserEmailDelete,
- "user.email.read": UserEmailRead,
- "user.email.update": UserEmailUpdate,
- "user.multifactor_authentication.create": UserMultifactorAuthenticationCreate,
- "user.multifactor_authentication.delete": UserMultifactorAuthenticationDelete,
- "user.multifactor_authentication.read": UserMultifactorAuthenticationRead,
- "user.multifactor_authentication.update": UserMultifactorAuthenticationUpdate,
- "user.password.read": UserPasswordRead,
- "user.password.update": UserPasswordUpdate,
- "user.profile.read": UserProfileRead,
- "user.profile.update": UserProfileUpdate,
- "user.scheduled_sends.create": UserScheduledSendsCreate,
- "user.scheduled_sends.delete": UserScheduledSendsDelete,
- "user.scheduled_sends.read": UserScheduledSendsRead,
- "user.scheduled_sends.update": UserScheduledSendsUpdate,
- "user.settings.enforced_tls.read": UserSettingsEnforcedTlsRead,
- "user.settings.enforced_tls.update": UserSettingsEnforcedTlsUpdate,
- "user.timezone.read": UserTimezoneRead,
- "user.username.read": UserUsernameRead,
- "user.username.update": UserUsernameUpdate,
- "user.webhooks.event.settings.read": UserWebhooksEventSettingsRead,
- "user.webhooks.event.settings.update": UserWebhooksEventSettingsUpdate,
- "user.webhooks.event.test.create": UserWebhooksEventTestCreate,
- "user.webhooks.event.test.read": UserWebhooksEventTestRead,
- "user.webhooks.event.test.update": UserWebhooksEventTestUpdate,
- "user.webhooks.parse.settings.create": UserWebhooksParseSettingsCreate,
- "user.webhooks.parse.settings.delete": UserWebhooksParseSettingsDelete,
- "user.webhooks.parse.settings.read": UserWebhooksParseSettingsRead,
- "user.webhooks.parse.settings.update": UserWebhooksParseSettingsUpdate,
- "user.webhooks.parse.stats.read": UserWebhooksParseStatsRead,
- "whitelabel.create": WhitelabelCreate,
- "whitelabel.delete": WhitelabelDelete,
- "whitelabel.read": WhitelabelRead,
- "whitelabel.update": WhitelabelUpdate,
- }
-
- PermissionIDs = map[Permission]int{
- AccessSettingsActivityRead: 1,
- AccessSettingsWhitelistCreate: 2,
- AccessSettingsWhitelistDelete: 3,
- AccessSettingsWhitelistRead: 4,
- AccessSettingsWhitelistUpdate: 5,
- AlertsCreate: 6,
- AlertsDelete: 7,
- AlertsRead: 8,
- AlertsUpdate: 9,
- ApiKeysCreate: 10,
- ApiKeysDelete: 11,
- ApiKeysRead: 12,
- ApiKeysUpdate: 13,
- AsmGroupsCreate: 14,
- AsmGroupsDelete: 15,
- AsmGroupsRead: 16,
- AsmGroupsUpdate: 17,
- BillingCreate: 18,
- BillingDelete: 19,
- BillingRead: 20,
- BillingUpdate: 21,
- BrowsersStatsRead: 22,
- CategoriesCreate: 23,
- CategoriesDelete: 24,
- CategoriesRead: 25,
- CategoriesStatsRead: 26,
- CategoriesStatsSumsRead: 27,
- CategoriesUpdate: 28,
- ClientsDesktopStatsRead: 29,
- ClientsPhoneStatsRead: 30,
- ClientsStatsRead: 31,
- ClientsTabletStatsRead: 32,
- ClientsWebmailStatsRead: 33,
- DevicesStatsRead: 34,
- EmailActivityRead: 35,
- GeoStatsRead: 36,
- IpsAssignedRead: 37,
- IpsPoolsCreate: 38,
- IpsPoolsDelete: 39,
- IpsPoolsIpsCreate: 40,
- IpsPoolsIpsDelete: 41,
- IpsPoolsIpsRead: 42,
- IpsPoolsIpsUpdate: 43,
- IpsPoolsRead: 44,
- IpsPoolsUpdate: 45,
- IpsRead: 46,
- IpsWarmupCreate: 47,
- IpsWarmupDelete: 48,
- IpsWarmupRead: 49,
- IpsWarmupUpdate: 50,
- MailSettingsAddressWhitelistRead: 51,
- MailSettingsAddressWhitelistUpdate: 52,
- MailSettingsBouncePurgeRead: 53,
- MailSettingsBouncePurgeUpdate: 54,
- MailSettingsFooterRead: 55,
- MailSettingsFooterUpdate: 56,
- MailSettingsForwardBounceRead: 57,
- MailSettingsForwardBounceUpdate: 58,
- MailSettingsForwardSpamRead: 59,
- MailSettingsForwardSpamUpdate: 60,
- MailSettingsPlainContentRead: 61,
- MailSettingsPlainContentUpdate: 62,
- MailSettingsRead: 63,
- MailSettingsTemplateRead: 64,
- MailSettingsTemplateUpdate: 65,
- MailBatchCreate: 66,
- MailBatchDelete: 67,
- MailBatchRead: 68,
- MailBatchUpdate: 69,
- MailSend: 70,
- MailboxProvidersStatsRead: 71,
- MarketingCampaignsCreate: 72,
- MarketingCampaignsDelete: 73,
- MarketingCampaignsRead: 74,
- MarketingCampaignsUpdate: 75,
- PartnerSettingsNewRelicRead: 76,
- PartnerSettingsNewRelicUpdate: 77,
- PartnerSettingsRead: 78,
- StatsGlobalRead: 79,
- StatsRead: 80,
- SubusersCreate: 81,
- SubusersCreditsCreate: 82,
- SubusersCreditsDelete: 83,
- SubusersCreditsRead: 84,
- SubusersCreditsRemainingCreate: 85,
- SubusersCreditsRemainingDelete: 86,
- SubusersCreditsRemainingRead: 87,
- SubusersCreditsRemainingUpdate: 88,
- SubusersCreditsUpdate: 89,
- SubusersDelete: 90,
- SubusersMonitorCreate: 91,
- SubusersMonitorDelete: 92,
- SubusersMonitorRead: 93,
- SubusersMonitorUpdate: 94,
- SubusersRead: 95,
- SubusersReputationsRead: 96,
- SubusersStatsMonthlyRead: 97,
- SubusersStatsRead: 98,
- SubusersStatsSumsRead: 99,
- SubusersSummaryRead: 100,
- SubusersUpdate: 101,
- SuppressionBlocksCreate: 102,
- SuppressionBlocksDelete: 103,
- SuppressionBlocksRead: 104,
- SuppressionBlocksUpdate: 105,
- SuppressionBouncesCreate: 106,
- SuppressionBouncesDelete: 107,
- SuppressionBouncesRead: 108,
- SuppressionBouncesUpdate: 109,
- SuppressionCreate: 110,
- SuppressionDelete: 111,
- SuppressionInvalidEmailsCreate: 112,
- SuppressionInvalidEmailsDelete: 113,
- SuppressionInvalidEmailsRead: 114,
- SuppressionInvalidEmailsUpdate: 115,
- SuppressionRead: 116,
- SuppressionSpamReportsCreate: 117,
- SuppressionSpamReportsDelete: 118,
- SuppressionSpamReportsRead: 119,
- SuppressionSpamReportsUpdate: 120,
- SuppressionUnsubscribesCreate: 121,
- SuppressionUnsubscribesDelete: 122,
- SuppressionUnsubscribesRead: 123,
- SuppressionUnsubscribesUpdate: 124,
- SuppressionUpdate: 125,
- TeammatesCreate: 126,
- TeammatesRead: 127,
- TeammatesUpdate: 128,
- TeammatesDelete: 129,
- TemplatesCreate: 130,
- TemplatesDelete: 131,
- TemplatesRead: 132,
- TemplatesUpdate: 133,
- TemplatesVersionsActivateCreate: 134,
- TemplatesVersionsActivateDelete: 135,
- TemplatesVersionsActivateRead: 136,
- TemplatesVersionsActivateUpdate: 137,
- TemplatesVersionsCreate: 138,
- TemplatesVersionsDelete: 139,
- TemplatesVersionsRead: 140,
- TemplatesVersionsUpdate: 141,
- TrackingSettingsClickRead: 142,
- TrackingSettingsClickUpdate: 143,
- TrackingSettingsGoogleAnalyticsRead: 144,
- TrackingSettingsGoogleAnalyticsUpdate: 145,
- TrackingSettingsOpenRead: 146,
- TrackingSettingsOpenUpdate: 147,
- TrackingSettingsRead: 148,
- TrackingSettingsSubscriptionRead: 149,
- TrackingSettingsSubscriptionUpdate: 150,
- UserAccountRead: 151,
- UserCreditsRead: 152,
- UserEmailCreate: 153,
- UserEmailDelete: 154,
- UserEmailRead: 155,
- UserEmailUpdate: 156,
- UserMultifactorAuthenticationCreate: 157,
- UserMultifactorAuthenticationDelete: 158,
- UserMultifactorAuthenticationRead: 159,
- UserMultifactorAuthenticationUpdate: 160,
- UserPasswordRead: 161,
- UserPasswordUpdate: 162,
- UserProfileRead: 163,
- UserProfileUpdate: 164,
- UserScheduledSendsCreate: 165,
- UserScheduledSendsDelete: 166,
- UserScheduledSendsRead: 167,
- UserScheduledSendsUpdate: 168,
- UserSettingsEnforcedTlsRead: 169,
- UserSettingsEnforcedTlsUpdate: 170,
- UserTimezoneRead: 171,
- UserUsernameRead: 172,
- UserUsernameUpdate: 173,
- UserWebhooksEventSettingsRead: 174,
- UserWebhooksEventSettingsUpdate: 175,
- UserWebhooksEventTestCreate: 176,
- UserWebhooksEventTestRead: 177,
- UserWebhooksEventTestUpdate: 178,
- UserWebhooksParseSettingsCreate: 179,
- UserWebhooksParseSettingsDelete: 180,
- UserWebhooksParseSettingsRead: 181,
- UserWebhooksParseSettingsUpdate: 182,
- UserWebhooksParseStatsRead: 183,
- WhitelabelCreate: 184,
- WhitelabelDelete: 185,
- WhitelabelRead: 186,
- WhitelabelUpdate: 187,
- }
-
- IdToPermission = map[int]Permission{
- 1: AccessSettingsActivityRead,
- 2: AccessSettingsWhitelistCreate,
- 3: AccessSettingsWhitelistDelete,
- 4: AccessSettingsWhitelistRead,
- 5: AccessSettingsWhitelistUpdate,
- 6: AlertsCreate,
- 7: AlertsDelete,
- 8: AlertsRead,
- 9: AlertsUpdate,
- 10: ApiKeysCreate,
- 11: ApiKeysDelete,
- 12: ApiKeysRead,
- 13: ApiKeysUpdate,
- 14: AsmGroupsCreate,
- 15: AsmGroupsDelete,
- 16: AsmGroupsRead,
- 17: AsmGroupsUpdate,
- 18: BillingCreate,
- 19: BillingDelete,
- 20: BillingRead,
- 21: BillingUpdate,
- 22: BrowsersStatsRead,
- 23: CategoriesCreate,
- 24: CategoriesDelete,
- 25: CategoriesRead,
- 26: CategoriesStatsRead,
- 27: CategoriesStatsSumsRead,
- 28: CategoriesUpdate,
- 29: ClientsDesktopStatsRead,
- 30: ClientsPhoneStatsRead,
- 31: ClientsStatsRead,
- 32: ClientsTabletStatsRead,
- 33: ClientsWebmailStatsRead,
- 34: DevicesStatsRead,
- 35: EmailActivityRead,
- 36: GeoStatsRead,
- 37: IpsAssignedRead,
- 38: IpsPoolsCreate,
- 39: IpsPoolsDelete,
- 40: IpsPoolsIpsCreate,
- 41: IpsPoolsIpsDelete,
- 42: IpsPoolsIpsRead,
- 43: IpsPoolsIpsUpdate,
- 44: IpsPoolsRead,
- 45: IpsPoolsUpdate,
- 46: IpsRead,
- 47: IpsWarmupCreate,
- 48: IpsWarmupDelete,
- 49: IpsWarmupRead,
- 50: IpsWarmupUpdate,
- 51: MailSettingsAddressWhitelistRead,
- 52: MailSettingsAddressWhitelistUpdate,
- 53: MailSettingsBouncePurgeRead,
- 54: MailSettingsBouncePurgeUpdate,
- 55: MailSettingsFooterRead,
- 56: MailSettingsFooterUpdate,
- 57: MailSettingsForwardBounceRead,
- 58: MailSettingsForwardBounceUpdate,
- 59: MailSettingsForwardSpamRead,
- 60: MailSettingsForwardSpamUpdate,
- 61: MailSettingsPlainContentRead,
- 62: MailSettingsPlainContentUpdate,
- 63: MailSettingsRead,
- 64: MailSettingsTemplateRead,
- 65: MailSettingsTemplateUpdate,
- 66: MailBatchCreate,
- 67: MailBatchDelete,
- 68: MailBatchRead,
- 69: MailBatchUpdate,
- 70: MailSend,
- 71: MailboxProvidersStatsRead,
- 72: MarketingCampaignsCreate,
- 73: MarketingCampaignsDelete,
- 74: MarketingCampaignsRead,
- 75: MarketingCampaignsUpdate,
- 76: PartnerSettingsNewRelicRead,
- 77: PartnerSettingsNewRelicUpdate,
- 78: PartnerSettingsRead,
- 79: StatsGlobalRead,
- 80: StatsRead,
- 81: SubusersCreate,
- 82: SubusersCreditsCreate,
- 83: SubusersCreditsDelete,
- 84: SubusersCreditsRead,
- 85: SubusersCreditsRemainingCreate,
- 86: SubusersCreditsRemainingDelete,
- 87: SubusersCreditsRemainingRead,
- 88: SubusersCreditsRemainingUpdate,
- 89: SubusersCreditsUpdate,
- 90: SubusersDelete,
- 91: SubusersMonitorCreate,
- 92: SubusersMonitorDelete,
- 93: SubusersMonitorRead,
- 94: SubusersMonitorUpdate,
- 95: SubusersRead,
- 96: SubusersReputationsRead,
- 97: SubusersStatsMonthlyRead,
- 98: SubusersStatsRead,
- 99: SubusersStatsSumsRead,
- 100: SubusersSummaryRead,
- 101: SubusersUpdate,
- 102: SuppressionBlocksCreate,
- 103: SuppressionBlocksDelete,
- 104: SuppressionBlocksRead,
- 105: SuppressionBlocksUpdate,
- 106: SuppressionBouncesCreate,
- 107: SuppressionBouncesDelete,
- 108: SuppressionBouncesRead,
- 109: SuppressionBouncesUpdate,
- 110: SuppressionCreate,
- 111: SuppressionDelete,
- 112: SuppressionInvalidEmailsCreate,
- 113: SuppressionInvalidEmailsDelete,
- 114: SuppressionInvalidEmailsRead,
- 115: SuppressionInvalidEmailsUpdate,
- 116: SuppressionRead,
- 117: SuppressionSpamReportsCreate,
- 118: SuppressionSpamReportsDelete,
- 119: SuppressionSpamReportsRead,
- 120: SuppressionSpamReportsUpdate,
- 121: SuppressionUnsubscribesCreate,
- 122: SuppressionUnsubscribesDelete,
- 123: SuppressionUnsubscribesRead,
- 124: SuppressionUnsubscribesUpdate,
- 125: SuppressionUpdate,
- 126: TeammatesCreate,
- 127: TeammatesRead,
- 128: TeammatesUpdate,
- 129: TeammatesDelete,
- 130: TemplatesCreate,
- 131: TemplatesDelete,
- 132: TemplatesRead,
- 133: TemplatesUpdate,
- 134: TemplatesVersionsActivateCreate,
- 135: TemplatesVersionsActivateDelete,
- 136: TemplatesVersionsActivateRead,
- 137: TemplatesVersionsActivateUpdate,
- 138: TemplatesVersionsCreate,
- 139: TemplatesVersionsDelete,
- 140: TemplatesVersionsRead,
- 141: TemplatesVersionsUpdate,
- 142: TrackingSettingsClickRead,
- 143: TrackingSettingsClickUpdate,
- 144: TrackingSettingsGoogleAnalyticsRead,
- 145: TrackingSettingsGoogleAnalyticsUpdate,
- 146: TrackingSettingsOpenRead,
- 147: TrackingSettingsOpenUpdate,
- 148: TrackingSettingsRead,
- 149: TrackingSettingsSubscriptionRead,
- 150: TrackingSettingsSubscriptionUpdate,
- 151: UserAccountRead,
- 152: UserCreditsRead,
- 153: UserEmailCreate,
- 154: UserEmailDelete,
- 155: UserEmailRead,
- 156: UserEmailUpdate,
- 157: UserMultifactorAuthenticationCreate,
- 158: UserMultifactorAuthenticationDelete,
- 159: UserMultifactorAuthenticationRead,
- 160: UserMultifactorAuthenticationUpdate,
- 161: UserPasswordRead,
- 162: UserPasswordUpdate,
- 163: UserProfileRead,
- 164: UserProfileUpdate,
- 165: UserScheduledSendsCreate,
- 166: UserScheduledSendsDelete,
- 167: UserScheduledSendsRead,
- 168: UserScheduledSendsUpdate,
- 169: UserSettingsEnforcedTlsRead,
- 170: UserSettingsEnforcedTlsUpdate,
- 171: UserTimezoneRead,
- 172: UserUsernameRead,
- 173: UserUsernameUpdate,
- 174: UserWebhooksEventSettingsRead,
- 175: UserWebhooksEventSettingsUpdate,
- 176: UserWebhooksEventTestCreate,
- 177: UserWebhooksEventTestRead,
- 178: UserWebhooksEventTestUpdate,
- 179: UserWebhooksParseSettingsCreate,
- 180: UserWebhooksParseSettingsDelete,
- 181: UserWebhooksParseSettingsRead,
- 182: UserWebhooksParseSettingsUpdate,
- 183: UserWebhooksParseStatsRead,
- 184: WhitelabelCreate,
- 185: WhitelabelDelete,
- 186: WhitelabelRead,
- 187: WhitelabelUpdate,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/sendgrid/permissions.yaml b/pkg/analyzer/analyzers/sendgrid/permissions.yaml
deleted file mode 100644
index 80415dee4e35..000000000000
--- a/pkg/analyzer/analyzers/sendgrid/permissions.yaml
+++ /dev/null
@@ -1,188 +0,0 @@
-permissions:
- - access_settings.activity.read
- - access_settings.whitelist.create
- - access_settings.whitelist.delete
- - access_settings.whitelist.read
- - access_settings.whitelist.update
- - alerts.create
- - alerts.delete
- - alerts.read
- - alerts.update
- - api_keys.create
- - api_keys.delete
- - api_keys.read
- - api_keys.update
- - asm.groups.create
- - asm.groups.delete
- - asm.groups.read
- - asm.groups.update
- - billing.create
- - billing.delete
- - billing.read
- - billing.update
- - browsers.stats.read
- - categories.create
- - categories.delete
- - categories.read
- - categories.stats.read
- - categories.stats.sums.read
- - categories.update
- - clients.desktop.stats.read
- - clients.phone.stats.read
- - clients.stats.read
- - clients.tablet.stats.read
- - clients.webmail.stats.read
- - devices.stats.read
- - email_activity.read
- - geo.stats.read
- - ips.assigned.read
- - ips.pools.create
- - ips.pools.delete
- - ips.pools.ips.create
- - ips.pools.ips.delete
- - ips.pools.ips.read
- - ips.pools.ips.update
- - ips.pools.read
- - ips.pools.update
- - ips.read
- - ips.warmup.create
- - ips.warmup.delete
- - ips.warmup.read
- - ips.warmup.update
- - mail_settings.address_whitelist.read
- - mail_settings.address_whitelist.update
- - mail_settings.bounce_purge.read
- - mail_settings.bounce_purge.update
- - mail_settings.footer.read
- - mail_settings.footer.update
- - mail_settings.forward_bounce.read
- - mail_settings.forward_bounce.update
- - mail_settings.forward_spam.read
- - mail_settings.forward_spam.update
- - mail_settings.plain_content.read
- - mail_settings.plain_content.update
- - mail_settings.read
- - mail_settings.template.read
- - mail_settings.template.update
- - mail.batch.create
- - mail.batch.delete
- - mail.batch.read
- - mail.batch.update
- - mail.send
- - mailbox_providers.stats.read
- - marketing_campaigns.create
- - marketing_campaigns.delete
- - marketing_campaigns.read
- - marketing_campaigns.update
- - partner_settings.new_relic.read
- - partner_settings.new_relic.update
- - partner_settings.read
- - stats.global.read
- - stats.read
- - subusers.create
- - subusers.credits.create
- - subusers.credits.delete
- - subusers.credits.read
- - subusers.credits.remaining.create
- - subusers.credits.remaining.delete
- - subusers.credits.remaining.read
- - subusers.credits.remaining.update
- - subusers.credits.update
- - subusers.delete
- - subusers.monitor.create
- - subusers.monitor.delete
- - subusers.monitor.read
- - subusers.monitor.update
- - subusers.read
- - subusers.reputations.read
- - subusers.stats.monthly.read
- - subusers.stats.read
- - subusers.stats.sums.read
- - subusers.summary.read
- - subusers.update
- - suppression.blocks.create
- - suppression.blocks.delete
- - suppression.blocks.read
- - suppression.blocks.update
- - suppression.bounces.create
- - suppression.bounces.delete
- - suppression.bounces.read
- - suppression.bounces.update
- - suppression.create
- - suppression.delete
- - suppression.invalid_emails.create
- - suppression.invalid_emails.delete
- - suppression.invalid_emails.read
- - suppression.invalid_emails.update
- - suppression.read
- - suppression.spam_reports.create
- - suppression.spam_reports.delete
- - suppression.spam_reports.read
- - suppression.spam_reports.update
- - suppression.unsubscribes.create
- - suppression.unsubscribes.delete
- - suppression.unsubscribes.read
- - suppression.unsubscribes.update
- - suppression.update
- - teammates.create
- - teammates.read
- - teammates.update
- - teammates.delete
- - templates.create
- - templates.delete
- - templates.read
- - templates.update
- - templates.versions.activate.create
- - templates.versions.activate.delete
- - templates.versions.activate.read
- - templates.versions.activate.update
- - templates.versions.create
- - templates.versions.delete
- - templates.versions.read
- - templates.versions.update
- - tracking_settings.click.read
- - tracking_settings.click.update
- - tracking_settings.google_analytics.read
- - tracking_settings.google_analytics.update
- - tracking_settings.open.read
- - tracking_settings.open.update
- - tracking_settings.read
- - tracking_settings.subscription.read
- - tracking_settings.subscription.update
- - user.account.read
- - user.credits.read
- - user.email.create
- - user.email.delete
- - user.email.read
- - user.email.update
- - user.multifactor_authentication.create
- - user.multifactor_authentication.delete
- - user.multifactor_authentication.read
- - user.multifactor_authentication.update
- - user.password.read
- - user.password.update
- - user.profile.read
- - user.profile.update
- - user.scheduled_sends.create
- - user.scheduled_sends.delete
- - user.scheduled_sends.read
- - user.scheduled_sends.update
- - user.settings.enforced_tls.read
- - user.settings.enforced_tls.update
- - user.timezone.read
- - user.username.read
- - user.username.update
- - user.webhooks.event.settings.read
- - user.webhooks.event.settings.update
- - user.webhooks.event.test.create
- - user.webhooks.event.test.read
- - user.webhooks.event.test.update
- - user.webhooks.parse.settings.create
- - user.webhooks.parse.settings.delete
- - user.webhooks.parse.settings.read
- - user.webhooks.parse.settings.update
- - user.webhooks.parse.stats.read
- - whitelabel.create
- - whitelabel.delete
- - whitelabel.read
- - whitelabel.update
diff --git a/pkg/analyzer/analyzers/sendgrid/result_output.json b/pkg/analyzer/analyzers/sendgrid/result_output.json
deleted file mode 100644
index c82ba7620723..000000000000
--- a/pkg/analyzer/analyzers/sendgrid/result_output.json
+++ /dev/null
@@ -1,2596 +0,0 @@
-{
- "AnalyzerType": 16,
- "Bindings": [
- {
- "Resource": {
- "Name": "API Keys",
- "FullyQualifiedName": "API Keys",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "api_keys.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "API Keys",
- "FullyQualifiedName": "API Keys",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "api_keys.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "API Keys",
- "FullyQualifiedName": "API Keys",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "api_keys.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "API Keys",
- "FullyQualifiedName": "API Keys",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "api_keys.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Account",
- "FullyQualifiedName": "User Account/Account",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.account.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Address Allow List",
- "FullyQualifiedName": "Mail Settings/Address Allow List",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.address_whitelist.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Address Allow List",
- "FullyQualifiedName": "Mail Settings/Address Allow List",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.address_whitelist.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Alerts",
- "FullyQualifiedName": "Alerts",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "alerts.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Alerts",
- "FullyQualifiedName": "Alerts",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "alerts.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Alerts",
- "FullyQualifiedName": "Alerts",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "alerts.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Alerts",
- "FullyQualifiedName": "Alerts",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "alerts.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Bounce Purge",
- "FullyQualifiedName": "Mail Settings/Bounce Purge",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.bounce_purge.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Bounce Purge",
- "FullyQualifiedName": "Mail Settings/Bounce Purge",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.bounce_purge.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Browser Stats",
- "FullyQualifiedName": "Stats/Browser Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "browsers.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Category Management",
- "FullyQualifiedName": "Category Management",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "categories.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Category Management",
- "FullyQualifiedName": "Category Management",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "categories.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Category Management",
- "FullyQualifiedName": "Category Management",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "categories.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Category Management",
- "FullyQualifiedName": "Category Management",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "categories.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Category Management",
- "FullyQualifiedName": "Category Management",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "categories.stats.sums.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Category Management",
- "FullyQualifiedName": "Category Management",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "categories.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Click Tracking",
- "FullyQualifiedName": "Tracking/Click Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Tracking",
- "FullyQualifiedName": "Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "tracking_settings.click.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Click Tracking",
- "FullyQualifiedName": "Tracking/Click Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Tracking",
- "FullyQualifiedName": "Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "tracking_settings.click.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Credits",
- "FullyQualifiedName": "User Account/Credits",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.credits.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Email",
- "FullyQualifiedName": "User Account/Email",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.email.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Email",
- "FullyQualifiedName": "User Account/Email",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.email.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Email",
- "FullyQualifiedName": "User Account/Email",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.email.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Email",
- "FullyQualifiedName": "User Account/Email",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.email.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Email Clients and Devices",
- "FullyQualifiedName": "Stats/Email Clients and Devices",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "clients.desktop.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Email Clients and Devices",
- "FullyQualifiedName": "Stats/Email Clients and Devices",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "clients.phone.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Email Clients and Devices",
- "FullyQualifiedName": "Stats/Email Clients and Devices",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "clients.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Email Clients and Devices",
- "FullyQualifiedName": "Stats/Email Clients and Devices",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "clients.tablet.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Email Clients and Devices",
- "FullyQualifiedName": "Stats/Email Clients and Devices",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "clients.webmail.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Email Clients and Devices",
- "FullyQualifiedName": "Stats/Email Clients and Devices",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "devices.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Enforced TLS",
- "FullyQualifiedName": "User Account/Enforced TLS",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.settings.enforced_tls.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Enforced TLS",
- "FullyQualifiedName": "User Account/Enforced TLS",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.settings.enforced_tls.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Event Notification",
- "FullyQualifiedName": "Mail Settings/Event Notification",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.webhooks.event.settings.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Event Notification",
- "FullyQualifiedName": "Mail Settings/Event Notification",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.webhooks.event.settings.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Event Notification",
- "FullyQualifiedName": "Mail Settings/Event Notification",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.webhooks.event.test.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Event Notification",
- "FullyQualifiedName": "Mail Settings/Event Notification",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.webhooks.event.test.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Event Notification",
- "FullyQualifiedName": "Mail Settings/Event Notification",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.webhooks.event.test.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Footer",
- "FullyQualifiedName": "Mail Settings/Footer",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.footer.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Footer",
- "FullyQualifiedName": "Mail Settings/Footer",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.footer.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Forward Bounce",
- "FullyQualifiedName": "Mail Settings/Forward Bounce",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.forward_bounce.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Forward Bounce",
- "FullyQualifiedName": "Mail Settings/Forward Bounce",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.forward_bounce.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Forward Spam",
- "FullyQualifiedName": "Mail Settings/Forward Spam",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.forward_spam.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Forward Spam",
- "FullyQualifiedName": "Mail Settings/Forward Spam",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.forward_spam.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Geographical",
- "FullyQualifiedName": "Stats/Geographical",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "geo.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Global Stats",
- "FullyQualifiedName": "Stats/Global Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "stats.global.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Google Analytics",
- "FullyQualifiedName": "Tracking/Google Analytics",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Tracking",
- "FullyQualifiedName": "Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "tracking_settings.google_analytics.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Google Analytics",
- "FullyQualifiedName": "Tracking/Google Analytics",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Tracking",
- "FullyQualifiedName": "Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "tracking_settings.google_analytics.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "IP Management",
- "FullyQualifiedName": "IP Management",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "ips.pools.ips.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Inbound Parse",
- "FullyQualifiedName": "Inbound Parse",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "user.webhooks.parse.settings.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Inbound Parse",
- "FullyQualifiedName": "Inbound Parse",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "user.webhooks.parse.settings.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Inbound Parse",
- "FullyQualifiedName": "Inbound Parse",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "user.webhooks.parse.settings.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Inbound Parse",
- "FullyQualifiedName": "Inbound Parse",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "user.webhooks.parse.settings.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Legacy Email Template",
- "FullyQualifiedName": "Mail Settings/Legacy Email Template",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.template.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Legacy Email Template",
- "FullyQualifiedName": "Mail Settings/Legacy Email Template",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.template.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Mail Send",
- "FullyQualifiedName": "Mail Send/Mail Send",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Send",
- "FullyQualifiedName": "Mail Send",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail.send",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "mail_settings.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Mailbox Provider Stats",
- "FullyQualifiedName": "Stats/Mailbox Provider Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mailbox_providers.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Multifactor Authentication",
- "FullyQualifiedName": "User Account/Multifactor Authentication",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.multifactor_authentication.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Multifactor Authentication",
- "FullyQualifiedName": "User Account/Multifactor Authentication",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.multifactor_authentication.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Multifactor Authentication",
- "FullyQualifiedName": "User Account/Multifactor Authentication",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.multifactor_authentication.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Multifactor Authentication",
- "FullyQualifiedName": "User Account/Multifactor Authentication",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.multifactor_authentication.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Open Tracking",
- "FullyQualifiedName": "Tracking/Open Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Tracking",
- "FullyQualifiedName": "Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "tracking_settings.open.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Open Tracking",
- "FullyQualifiedName": "Tracking/Open Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Tracking",
- "FullyQualifiedName": "Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "tracking_settings.open.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Parse Webhook",
- "FullyQualifiedName": "Stats/Parse Webhook",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.webhooks.parse.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Partners",
- "FullyQualifiedName": "Partners",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "partner_settings.new_relic.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Partners",
- "FullyQualifiedName": "Partners",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "partner_settings.new_relic.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Partners",
- "FullyQualifiedName": "Partners",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "partner_settings.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Password",
- "FullyQualifiedName": "User Account/Password",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.password.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Password",
- "FullyQualifiedName": "User Account/Password",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.password.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Plain Content",
- "FullyQualifiedName": "Mail Settings/Plain Content",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.plain_content.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Plain Content",
- "FullyQualifiedName": "Mail Settings/Plain Content",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "mail_settings.plain_content.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Profile",
- "FullyQualifiedName": "User Account/Profile",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.profile.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Profile",
- "FullyQualifiedName": "User Account/Profile",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.profile.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Security",
- "FullyQualifiedName": "Security",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "access_settings.activity.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Security",
- "FullyQualifiedName": "Security",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "access_settings.whitelist.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Security",
- "FullyQualifiedName": "Security",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "access_settings.whitelist.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Security",
- "FullyQualifiedName": "Security",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "access_settings.whitelist.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Security",
- "FullyQualifiedName": "Security",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "access_settings.whitelist.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sender Authentication",
- "FullyQualifiedName": "Sender Authentication",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "whitelabel.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sender Authentication",
- "FullyQualifiedName": "Sender Authentication",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "whitelabel.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sender Authentication",
- "FullyQualifiedName": "Sender Authentication",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "whitelabel.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Sender Authentication",
- "FullyQualifiedName": "Sender Authentication",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "whitelabel.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Source Integration",
- "FullyQualifiedName": "37006899",
- "Type": "User",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Stats Overview",
- "FullyQualifiedName": "Stats/Stats Overview",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Subscription Tracking",
- "FullyQualifiedName": "Tracking/Subscription Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Tracking",
- "FullyQualifiedName": "Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "tracking_settings.subscription.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Subscription Tracking",
- "FullyQualifiedName": "Tracking/Subscription Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Tracking",
- "FullyQualifiedName": "Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "tracking_settings.subscription.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Subuser Stats",
- "FullyQualifiedName": "Stats/Subuser Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "subusers.stats.monthly.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Subuser Stats",
- "FullyQualifiedName": "Stats/Subuser Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "subusers.stats.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Subuser Stats",
- "FullyQualifiedName": "Stats/Subuser Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "subusers.stats.sums.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.blocks.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.blocks.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.blocks.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.blocks.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.bounces.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.bounces.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.bounces.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.bounces.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.invalid_emails.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.invalid_emails.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.invalid_emails.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.invalid_emails.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.spam_reports.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.spam_reports.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.spam_reports.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.spam_reports.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.unsubscribes.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.unsubscribes.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.unsubscribes.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.unsubscribes.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Supressions",
- "FullyQualifiedName": "Suppressions/Supressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "suppression.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Teammates",
- "FullyQualifiedName": "Teammates",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "teammates.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Teammates",
- "FullyQualifiedName": "Teammates",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "teammates.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Teammates",
- "FullyQualifiedName": "Teammates",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "teammates.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Teammates",
- "FullyQualifiedName": "Teammates",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "teammates.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.versions.activate.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.versions.activate.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.versions.activate.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.versions.activate.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.versions.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.versions.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.versions.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Template Engine",
- "FullyQualifiedName": "Template Engine",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "templates.versions.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Timezone",
- "FullyQualifiedName": "User Account/Timezone",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.timezone.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Tracking",
- "FullyQualifiedName": "Tracking",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "tracking_settings.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Unsubscribe Groups",
- "FullyQualifiedName": "Suppressions/Unsubscribe Groups",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "asm.groups.create",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Unsubscribe Groups",
- "FullyQualifiedName": "Suppressions/Unsubscribe Groups",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "asm.groups.delete",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Unsubscribe Groups",
- "FullyQualifiedName": "Suppressions/Unsubscribe Groups",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "asm.groups.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Unsubscribe Groups",
- "FullyQualifiedName": "Suppressions/Unsubscribe Groups",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "asm.groups.update",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Username",
- "FullyQualifiedName": "User Account/Username",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.username.read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Username",
- "FullyQualifiedName": "User Account/Username",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "User Account",
- "FullyQualifiedName": "User Account",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- "Permission": {
- "Value": "user.username.update",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": [
- {
- "Name": "Billing",
- "FullyQualifiedName": "Billing",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- {
- "Name": "Design Library",
- "FullyQualifiedName": "Design Library",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- {
- "Name": "Email Activity",
- "FullyQualifiedName": "Email Activity",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- {
- "Name": "Email Testing",
- "FullyQualifiedName": "Email Testing",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- {
- "Name": "Scheduled Sends",
- "FullyQualifiedName": "Mail Send/Scheduled Sends",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Send",
- "FullyQualifiedName": "Mail Send",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "BCC",
- "FullyQualifiedName": "Mail Settings/BCC",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "Spam Checker",
- "FullyQualifiedName": "Mail Settings/Spam Checker",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Mail Settings",
- "FullyQualifiedName": "Mail Settings",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "Automation",
- "FullyQualifiedName": "Marketing/Automation",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Marketing",
- "FullyQualifiedName": "Marketing",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "Marketing",
- "FullyQualifiedName": "Marketing/Marketing",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Marketing",
- "FullyQualifiedName": "Marketing",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "Recipients Data Erasure",
- "FullyQualifiedName": "Recipients Data Erasure",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- {
- "Name": "Category Stats",
- "FullyQualifiedName": "Stats/Category Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Stats",
- "FullyQualifiedName": "Stats",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "Unsubscribe Group Suppressions",
- "FullyQualifiedName": "Suppressions/Unsubscribe Group Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "Global Suppressions",
- "FullyQualifiedName": "Suppressions/Global Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "Credentials",
- "FullyQualifiedName": "Credentials",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- {
- "Name": "Signup",
- "FullyQualifiedName": "Signup",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- },
- {
- "Name": "Blocks",
- "FullyQualifiedName": "Suppressions/Blocks",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "Bounces",
- "FullyQualifiedName": "Suppressions/Bounces",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "Invalid Emails",
- "FullyQualifiedName": "Suppressions/Invalid Emails",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "Spam Reports",
- "FullyQualifiedName": "Suppressions/Spam Reports",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "Unsubscribes",
- "FullyQualifiedName": "Suppressions/Unsubscribes",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "Suppressions",
- "FullyQualifiedName": "Suppressions",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- },
- {
- "Name": "UI",
- "FullyQualifiedName": "UI",
- "Type": "category",
- "Metadata": null,
- "Parent": null
- }
- ],
- "Metadata": {
- "2fa_required": true,
- "key_type": "full access"
- }
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/sendgrid/scopes.go b/pkg/analyzer/analyzers/sendgrid/scopes.go
deleted file mode 100644
index 9933cabc7fcb..000000000000
--- a/pkg/analyzer/analyzers/sendgrid/scopes.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package sendgrid
-
-import (
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
-)
-
-type SendgridScope struct {
- Category string
- SubCategory string
- Prefixes []string // Prefixes for the scope
- Permissions []string
- PermissionType analyzers.PermissionType
-}
-
-func (s *SendgridScope) AddPermission(permission string) {
- s.Permissions = append(s.Permissions, permission)
-}
-
-func (s *SendgridScope) RunTests() {
- if len(s.Permissions) == 0 {
- s.PermissionType = analyzers.NONE
- return
- }
- for _, permission := range s.Permissions {
- if strings.Contains(permission, ".read") {
- s.PermissionType = analyzers.READ
- } else {
- s.PermissionType = analyzers.READ_WRITE
- return
- }
- }
-}
-
-var SCOPES = []SendgridScope{
- // Billing
- {Category: "Billing", Prefixes: []string{"billing"}},
- // Restricted Access
- {Category: "API Keys", Prefixes: []string{"api_keys"}},
- {Category: "Alerts", Prefixes: []string{"alerts"}},
- {Category: "Category Management", Prefixes: []string{"categories"}},
- {Category: "Design Library", Prefixes: []string{"design_library"}},
- {Category: "Email Activity", Prefixes: []string{"messages"}},
- {Category: "Email Testing", Prefixes: []string{"email_testing"}},
- {Category: "IP Management", Prefixes: []string{"ips"}},
- {Category: "Inbound Parse", Prefixes: []string{"user.webhooks.parse.settings"}},
- {Category: "Mail Send", SubCategory: "Mail Send", Prefixes: []string{"mail.send"}},
- {Category: "Mail Send", SubCategory: "Scheduled Sends", Prefixes: []string{"user.scheduled_sends, mail.batch"}},
- {Category: "Mail Settings", SubCategory: "Address Allow List", Prefixes: []string{"mail_settings.address_whitelist"}},
- {Category: "Mail Settings", SubCategory: "BCC", Prefixes: []string{"mail_settings.bcc"}},
- {Category: "Mail Settings", SubCategory: "Bounce Purge", Prefixes: []string{"mail_settings.bounce_purge"}},
- {Category: "Mail Settings", SubCategory: "Event Notification", Prefixes: []string{"user.webhooks.event"}},
- {Category: "Mail Settings", SubCategory: "Footer", Prefixes: []string{"mail_settings.footer"}},
- {Category: "Mail Settings", SubCategory: "Forward Bounce", Prefixes: []string{"mail_settings.forward_bounce"}},
- {Category: "Mail Settings", SubCategory: "Forward Spam", Prefixes: []string{"mail_settings.forward_spam"}},
- {Category: "Mail Settings", SubCategory: "Legacy Email Template", Prefixes: []string{"mail_settings.template"}},
- {Category: "Mail Settings", SubCategory: "Plain Content", Prefixes: []string{"mail_settings.plain_content"}},
- {Category: "Mail Settings", SubCategory: "Spam Checker", Prefixes: []string{"mail_settings.spam_check"}},
- {Category: "Marketing", SubCategory: "Automation", Prefixes: []string{"marketing.automation"}},
- {Category: "Marketing", SubCategory: "Marketing", Prefixes: []string{"marketing.read"}},
- {Category: "Partners", Prefixes: []string{"partner_settings"}},
- {Category: "Recipients Data Erasure", Prefixes: []string{"recipients"}},
- {Category: "Security", Prefixes: []string{"access_settings"}},
- {Category: "Sender Authentication", Prefixes: []string{"whitelabel"}},
- {Category: "Stats", SubCategory: "Browser Stats", Prefixes: []string{"browsers"}},
- {Category: "Stats", SubCategory: "Category Stats", Prefixes: []string{"categories.stats"}},
- {Category: "Stats", SubCategory: "Email Clients and Devices", Prefixes: []string{"clients", "devices"}},
- {Category: "Stats", SubCategory: "Geographical", Prefixes: []string{"geo"}},
- {Category: "Stats", SubCategory: "Global Stats", Prefixes: []string{"stats.global"}},
- {Category: "Stats", SubCategory: "Mailbox Provider Stats", Prefixes: []string{"mailbox_providers"}},
- {Category: "Stats", SubCategory: "Parse Webhook", Prefixes: []string{"user.webhooks.parse.stats"}},
- {Category: "Stats", SubCategory: "Stats Overview", Prefixes: []string{"stats.read"}},
- {Category: "Stats", SubCategory: "Subuser Stats", Prefixes: []string{"subusers"}},
- {Category: "Suppressions", SubCategory: "Supressions", Prefixes: []string{"suppression"}},
- {Category: "Suppressions", SubCategory: "Unsubscribe Groups", Prefixes: []string{"asm.groups"}},
- {Category: "Template Engine", Prefixes: []string{"templates"}},
- {Category: "Tracking", SubCategory: "Click Tracking", Prefixes: []string{"tracking_settings.click"}},
- {Category: "Tracking", SubCategory: "Google Analytics", Prefixes: []string{"tracking_settings.google_analytics"}},
- {Category: "Tracking", SubCategory: "Open Tracking", Prefixes: []string{"tracking_settings.open"}},
- {Category: "Tracking", SubCategory: "Subscription Tracking", Prefixes: []string{"tracking_settings.subscription"}},
- {Category: "User Account", SubCategory: "Enforced TLS", Prefixes: []string{"user.settings.enforced_tls"}},
- {Category: "User Account", SubCategory: "Timezone", Prefixes: []string{"user.timezone"}},
- // Full Access Additional Categories
- {Category: "Suppressions", SubCategory: "Unsubscribe Group Suppressions", Prefixes: []string{"asm.groups.suppressions"}},
- {Category: "Suppressions", SubCategory: "Global Suppressions", Prefixes: []string{"asm.suppressions.global"}},
- {Category: "Credentials", Prefixes: []string{"credentials"}},
- {Category: "Mail Settings", Prefixes: []string{"mail_settings"}},
- {Category: "Signup", Prefixes: []string{"signup"}},
- {Category: "Suppressions", SubCategory: "Blocks", Prefixes: []string{"suppression.blocks"}},
- {Category: "Suppressions", SubCategory: "Bounces", Prefixes: []string{"suppression.bounces"}},
- {Category: "Suppressions", SubCategory: "Invalid Emails", Prefixes: []string{"suppression.invalid_emails"}},
- {Category: "Suppressions", SubCategory: "Spam Reports", Prefixes: []string{"suppression.spam_reports"}},
- {Category: "Suppressions", SubCategory: "Unsubscribes", Prefixes: []string{"suppression.unsubscribes"}},
- {Category: "Teammates", Prefixes: []string{"teammates"}},
- {Category: "Tracking", Prefixes: []string{"tracking_settings"}},
- {Category: "UI", Prefixes: []string{"ui"}},
- {Category: "User Account", SubCategory: "Account", Prefixes: []string{"user.account"}},
- {Category: "User Account", SubCategory: "Credits", Prefixes: []string{"user.credits"}},
- {Category: "User Account", SubCategory: "Email", Prefixes: []string{"user.email"}},
- {Category: "User Account", SubCategory: "Multifactor Authentication", Prefixes: []string{"user.multifactor_authentication"}},
- {Category: "User Account", SubCategory: "Password", Prefixes: []string{"user.password"}},
- {Category: "User Account", SubCategory: "Profile", Prefixes: []string{"user.profile"}},
- {Category: "User Account", SubCategory: "Username", Prefixes: []string{"user.username"}},
-}
diff --git a/pkg/analyzer/analyzers/sendgrid/sendgrid.go b/pkg/analyzer/analyzers/sendgrid/sendgrid.go
deleted file mode 100644
index 894680df2da1..000000000000
--- a/pkg/analyzer/analyzers/sendgrid/sendgrid.go
+++ /dev/null
@@ -1,322 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go sendgrid
-
-package sendgrid
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "slices"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- sg "github.com/sendgrid/sendgrid-go"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-type ScopesJSON struct {
- Scopes []string `json:"scopes"`
-}
-
-type Profile struct {
- ID int `json:"userid"`
- FirstName string `json:"first_name"`
- LastName string `json:"last_name"`
- Company string `json:"company"`
- Website string `json:"website"`
- Country string `json:"country"`
-}
-type SecretInfo struct {
- User Profile
- RawScopes []string
- Scopes []SendgridScope
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeSendgrid }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, fmt.Errorf("missing key in credInfo")
- }
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[!] Error: %v", err)
- return
- }
-
- color.Green("[!] Valid Sendgrid API Key\n\n")
-
- if slices.Contains(info.RawScopes, "user.email.read") {
- color.Green("[*] Sendgrid Key Type: Full Access Key")
- } else if slices.Contains(info.RawScopes, "billing.read") {
- color.Yellow("[*] Sendgrid Key Type: Billing Access Key")
- } else {
- color.Yellow("[*] Sendgrid Key Type: Restricted Access Key")
- }
-
- if slices.Contains(info.RawScopes, "2fa_required") {
- color.Yellow("[i] 2FA Required for this account")
- }
-
- if info.User.FirstName != "" {
- printProfile(info.User)
- }
-
- printPermissions(info, cfg.ShowAll)
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- // Setup custom HTTP client so we can log requests.
- sg.DefaultClient.HTTPClient = analyzers.NewAnalyzeClient(cfg)
-
- // get scopes
- rawScopes, err := getScopes(key)
- if err != nil {
- return nil, err
- }
-
- categoryScope := processPermissions(rawScopes)
-
- var secretInfo = &SecretInfo{
- RawScopes: rawScopes,
- Scopes: categoryScope,
- }
-
- if slices.Contains(rawScopes, "user.email.read") {
- profile, err := getProfile(key)
- if err != nil {
- // if get profile fails return secretInfo with scopes for partial success
- return secretInfo, nil
- }
-
- secretInfo.User = *profile
- }
-
- return secretInfo, nil
-}
-
-func getScopes(key string) ([]string, error) {
- req := sg.GetRequest(key, "/v3/scopes", "https://api.sendgrid.com")
- req.Method = "GET"
- resp, err := sg.API(req)
- if resp.StatusCode == 401 || resp.StatusCode == 403 {
- return nil, fmt.Errorf("invalid api key")
- } else if resp.StatusCode != 200 {
- return nil, fmt.Errorf("%v", resp.StatusCode)
- }
- if err != nil {
- return nil, err
- }
-
- // Unmarshal the JSON response into a struct
- var jsonScopes ScopesJSON
- if err := json.Unmarshal([]byte(resp.Body), &jsonScopes); err != nil {
- return nil, err
- }
-
- return jsonScopes.Scopes, nil
-}
-
-func getProfile(key string) (*Profile, error) {
- req := sg.GetRequest(key, "/v3/user/profile", "https://api.sendgrid.com")
- req.Method = "GET"
- resp, err := sg.API(req)
- if resp.StatusCode == 401 || resp.StatusCode == 403 {
- return nil, fmt.Errorf("invalid api key")
- } else if resp.StatusCode != 200 {
- return nil, fmt.Errorf("%v", resp.StatusCode)
- }
- if err != nil {
- return nil, err
- }
-
- // Unmarshal the JSON response into a struct
- var profile Profile
- if err := json.Unmarshal([]byte(resp.Body), &profile); err != nil {
- return nil, err
- }
-
- return &profile, nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- var keyType string
- if slices.Contains(info.RawScopes, "user.email.read") {
- keyType = "full access"
- } else if slices.Contains(info.RawScopes, "billing.read") {
- keyType = "billing access"
- } else {
- keyType = "restricted access"
- }
-
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeSendgrid,
- Metadata: map[string]any{
- "key_type": keyType,
- "2fa_required": slices.Contains(info.RawScopes, "2fa_required"),
- },
- Bindings: []analyzers.Binding{},
- UnboundedResources: []analyzers.Resource{},
- }
-
- // add profile information to analyzer result
- if info.User.ID != 0 && info.User.FirstName != "" {
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: analyzers.Resource{
- Name: info.User.FirstName + " " + info.User.LastName,
- FullyQualifiedName: fmt.Sprintf("%d", info.User.ID),
- Type: "User",
- },
- Permission: analyzers.Permission{
- Value: "full_access", // if token has all permissions than we can get user information
- },
- })
- }
-
- for _, scope := range info.Scopes {
- resource := getCategoryResource(scope)
-
- if len(scope.Permissions) == 0 {
- result.UnboundedResources = append(result.UnboundedResources, *resource)
- continue
- }
-
- for _, permission := range scope.Permissions {
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: *resource,
- Permission: analyzers.Permission{
- Value: permission,
- },
- })
- }
- }
-
- return &result
-}
-
-func getCategoryResource(scope SendgridScope) *analyzers.Resource {
- categoryResource := &analyzers.Resource{
- Name: scope.Category,
- FullyQualifiedName: scope.Category,
- Type: "category",
- Metadata: nil,
- }
-
- if scope.SubCategory != "" {
- return &analyzers.Resource{
- Name: scope.SubCategory,
- FullyQualifiedName: fmt.Sprintf("%s/%s", scope.Category, scope.SubCategory),
- Type: "category",
- Metadata: nil,
- Parent: categoryResource,
- }
- }
-
- return categoryResource
-}
-
-// getCategoryFromScope returns the category for a given scope.
-// It will return the most specific category possible.
-// For example, if the scope is "mail.send.read", it will return "Mail Send", not just "Mail"
-// since it's searching "mail.send.read" -> "mail.send" -> "mail"
-func getScopeIndex(categories []SendgridScope, scope string) int {
- splitScope := strings.Split(scope, ".")
- for i := len(splitScope); i > 0; i-- {
- searchScope := strings.Join(splitScope[:i], ".")
- for i, s := range categories {
- for _, prefix := range s.Prefixes {
- if strings.HasPrefix(searchScope, prefix) {
- return i
- }
- }
- }
- }
- return -1
-}
-
-func processPermissions(rawScopes []string) []SendgridScope {
- categoryPermissions := make([]SendgridScope, len(SCOPES))
-
- // copy all scope categories to the categoryPermissions slice
- copy(categoryPermissions, SCOPES)
- for _, scope := range rawScopes {
- // Skip these scopes since they are not useful for this analysis
- if scope == "2fa_required" || scope == "sender_verification_eligible" {
- continue
- }
-
- // must be part of generated permissions
- if _, ok := StringToPermission[scope]; !ok {
- continue
- }
- ind := getScopeIndex(categoryPermissions, scope)
- if ind == -1 {
- //color.Red("[!] Scope not found: %v", scope)
- continue
- }
- s := &categoryPermissions[ind]
- s.AddPermission(scope)
- }
-
- // Run tests to determine the permission type
- for i := range categoryPermissions {
- categoryPermissions[i].RunTests()
- }
-
- return categoryPermissions
-}
-
-func printProfile(profile Profile) {
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
-
- t.AppendHeader(table.Row{"UserID", "Name", "Company", "Website", "Country"})
- t.AppendRow(table.Row{profile.ID, profile.FirstName + " " + profile.LastName, profile.Company, profile.Website, profile.Country})
-
- t.Render()
-}
-
-func printPermissions(info *SecretInfo, show_all bool) {
- fmt.Print("\n\n")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- if show_all {
- t.AppendHeader(table.Row{"Scope", "Sub-Scope", "Access", "Permissions"})
- } else {
- t.AppendHeader(table.Row{"Scope", "Sub-Scope", "Access"})
- }
- // Print the scopes
- for _, s := range info.Scopes {
- writer := analyzers.GetWriterFromStatus(s.PermissionType)
- if show_all {
- t.AppendRow([]interface{}{writer(s.Category), writer(s.SubCategory), writer(s.PermissionType), writer(strings.Join(s.Permissions, "\n"))})
- } else if s.PermissionType != analyzers.NONE {
- t.AppendRow([]interface{}{writer(s.Category), writer(s.SubCategory), writer(s.PermissionType)})
- }
- }
- t.Render()
- fmt.Print("\n\n")
-}
diff --git a/pkg/analyzer/analyzers/sendgrid/sendgrid_test.go b/pkg/analyzer/analyzers/sendgrid/sendgrid_test.go
deleted file mode 100644
index 43f561e86de9..000000000000
--- a/pkg/analyzer/analyzers/sendgrid/sendgrid_test.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package sendgrid
-
-import (
- _ "embed"
- "encoding/json"
- "fmt"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed result_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "Valid Sendgrid key",
- key: testSecrets.MustGetField("SENDGRID"),
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- fmt.Println(string(gotJSON))
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/shopify/expected_output.json b/pkg/analyzer/analyzers/shopify/expected_output.json
deleted file mode 100644
index 5161cad0a7fa..000000000000
--- a/pkg/analyzer/analyzers/shopify/expected_output.json
+++ /dev/null
@@ -1,177 +0,0 @@
-{
- "AnalyzerType": 15,
- "Bindings": [
- {
- "Resource": {
- "Name": "Analytics",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Analytics",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "My Store",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
- "Type": "shop",
- "Metadata": {
- "created_at": "2024-08-16T17:16:17+05:00"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Applications",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Applications",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "My Store",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
- "Type": "shop",
- "Metadata": {
- "created_at": "2024-08-16T17:16:17+05:00"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Assigned fulfillment orders",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Assigned fulfillment orders",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "My Store",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
- "Type": "shop",
- "Metadata": {
- "created_at": "2024-08-16T17:16:17+05:00"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Customers",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Customers",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "My Store",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
- "Type": "shop",
- "Metadata": {
- "created_at": "2024-08-16T17:16:17+05:00"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Discovery",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Discovery",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "My Store",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
- "Type": "shop",
- "Metadata": {
- "created_at": "2024-08-16T17:16:17+05:00"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Merchant-managed fulfillment orders",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Merchant-managed fulfillment orders",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "My Store",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
- "Type": "shop",
- "Metadata": {
- "created_at": "2024-08-16T17:16:17+05:00"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "Reports",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/Reports",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "My Store",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
- "Type": "shop",
- "Metadata": {
- "created_at": "2024-08-16T17:16:17+05:00"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "cart_transforms",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com/cart_transforms",
- "Type": "category",
- "Metadata": null,
- "Parent": {
- "Name": "My Store",
- "FullyQualifiedName": "727f01-d6.myshopify.com/detectors@trufflesec.com",
- "Type": "shop",
- "Metadata": {
- "created_at": "2024-08-16T17:16:17+05:00"
- },
- "Parent": null
- }
- },
- "Permission": {
- "Value": "full_access",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": {
- "status_code": 200
- }
- }
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/shopify/permissions.go b/pkg/analyzer/analyzers/shopify/permissions.go
deleted file mode 100644
index ce9c7097feee..000000000000
--- a/pkg/analyzer/analyzers/shopify/permissions.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package shopify
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- Read Permission = iota
- Write Permission = iota
- FullAccess Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- Read: "read",
- Write: "write",
- FullAccess: "full_access",
- }
-
- StringToPermission = map[string]Permission{
- "read": Read,
- "write": Write,
- "full_access": FullAccess,
- }
-
- PermissionIDs = map[Permission]int{
- Read: 1,
- Write: 2,
- FullAccess: 3,
- }
-
- IdToPermission = map[int]Permission{
- 1: Read,
- 2: Write,
- 3: FullAccess,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/shopify/permissions.yaml b/pkg/analyzer/analyzers/shopify/permissions.yaml
deleted file mode 100644
index 7a87b27d1632..000000000000
--- a/pkg/analyzer/analyzers/shopify/permissions.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-permissions:
- - read
- - write
- - full_access
diff --git a/pkg/analyzer/analyzers/shopify/scopes.json b/pkg/analyzer/analyzers/shopify/scopes.json
deleted file mode 100644
index 8e82193faa63..000000000000
--- a/pkg/analyzer/analyzers/shopify/scopes.json
+++ /dev/null
@@ -1,470 +0,0 @@
-{
- "categories": {
- "Analytics": {
- "description": "View store metrics",
- "scopes": {
- "read_analytics": "Read"
- }
- },
- "Applications": {
- "description": "View or manage apps",
- "scopes": {
- "read_apps": "Read"
- }
- },
- "Assigned fulfillment orders": {
- "description": "View or manage fulfillment orders",
- "scopes": {
- "write_assigned_fulfillment_orders": "Write",
- "read_assigned_fulfillment_orders": "Read"
- }
- },
- "Browsing behavior": {
- "description": "View or manage online-store browsing behavior including page views, cart updates, product views and searches",
- "scopes": {
- "read_customer_events": "Read"
- }
- },
- "Custom pixels": {
- "description": "View or manage custom pixels",
- "scopes": {
- "write_custom_pixels": "Write",
- "read_custom_pixels": "Read"
- }
- },
- "Customers": {
- "description": "View or manage customers, customer addresses, order history, and customer groups",
- "scopes": {
- "write_customers": "Write",
- "read_customers": "Read"
- }
- },
- "Discounts": {
- "description": "View or manage automatic discounts and discount codes",
- "scopes": {
- "write_discounts": "Write",
- "read_discounts": "Read"
- }
- },
- "Discovery": {
- "description": "View or manage Discovery API",
- "scopes": {
- "write_discovery": "Write",
- "read_discovery": "Read"
- }
- },
- "Draft orders": {
- "description": "View or manage orders created by merchants on behalf of customers",
- "scopes": {
- "write_draft_orders": "Write",
- "read_draft_orders": "Read"
- }
- },
- "Files": {
- "description": "View or manage files",
- "scopes": {
- "write_files": "Write",
- "read_files": "Read"
- }
- },
- "Fulfillment services": {
- "description": "View or manage fulfillment services",
- "scopes": {
- "write_fulfillments": "Write",
- "read_fulfillments": "Read"
- }
- },
- "Gift cards": {
- "description": "View or manage gift cards",
- "scopes": {
- "write_gift_cards": "Write",
- "read_gift_cards": "Read"
- }
- },
- "Inventory": {
- "description": "View or manage inventory across multiple locations",
- "scopes": {
- "write_inventory": "Write",
- "read_inventory": "Read"
- }
- },
- "Legal policies": {
- "description": "View or manage a shop's legal policies",
- "scopes": {
- "write_legal_policies": "Write",
- "read_legal_policies": "Read"
- }
- },
- "Locations": {
- "description": "View the geographic location of stores, headquarters, and warehouses",
- "scopes": {
- "write_locations": "Write",
- "read_locations": "Read"
- }
- },
- "Marketing events": {
- "description": "View or manage marketing events and engagement data",
- "scopes": {
- "write_marketing_events": "Write",
- "read_marketing_events": "Read"
- }
- },
- "Merchant-managed fulfillment orders": {
- "description": "View or manage fulfillment orders assigned to merchant-managed locations",
- "scopes": {
- "write_merchant_managed_fulfillment_orders": "Write",
- "read_merchant_managed_fulfillment_orders": "Read"
- }
- },
- "Metaobject definitions": {
- "description": "View or manage definitions",
- "scopes": {
- "write_metaobject_definitions": "Write",
- "read_metaobject_definitions": "Read"
- }
- },
- "Metaobject entries": {
- "description": "View or manage entries",
- "scopes": {
- "write_metaobjects": "Write",
- "read_metaobjects": "Read"
- }
- },
- "Online Store navigation": {
- "description": "View menus for display on the storefront",
- "scopes": {
- "write_online_store_navigation": "Write",
- "read_online_store_navigation": "Read"
- }
- },
- "Online Store pages": {
- "description": "View or manage Online Store pages",
- "scopes": {
- "write_online_store_pages": "Write",
- "read_online_store_pages": "Read"
- }
- },
- "Order editing": {
- "description": "View or manage edits to orders",
- "scopes": {
- "write_order_edits": "Write",
- "read_order_edits": "Read"
- }
- },
- "Orders": {
- "description": "View or manage orders, transactions, fulfillments, and abandoned checkouts",
- "scopes": {
- "write_orders": "Write",
- "read_orders": "Read"
- }
- },
- "Packing slip management": {
- "description": "Edit and preview packing slip template",
- "scopes": {
- "write_packing_slip_templates": "Write",
- "read_packing_slip_templates": "Read"
- }
- },
- "Payment customizations": {
- "description": "View or manage payment customizations",
- "scopes": {
- "write_payment_customizations": "Write",
- "read_payment_customizations": "Read"
- }
- },
- "Payment terms": {
- "description": "View or manage payment terms",
- "scopes": {
- "write_payment_terms": "Write",
- "read_payment_terms": "Read"
- }
- },
- "Pixels": {
- "description": "View or manage pixels",
- "scopes": {
- "write_pixels": "Write",
- "read_pixels": "Read"
- }
- },
- "Price rules": {
- "description": "View or manage conditional discounts",
- "scopes": {
- "write_price_rules": "Write",
- "read_price_rules": "Read"
- }
- },
- "Product feeds": {
- "description": "View or manage product feeds",
- "scopes": {
- "write_product_feeds": "Write",
- "read_product_feeds": "Read"
- }
- },
- "Product listings": {
- "description": "View or manage product or collection listings",
- "scopes": {
- "write_product_listings": "Write",
- "read_product_listings": "Read"
- }
- },
- "Products": {
- "description": "View or manage products, variants, and collections",
- "scopes": {
- "write_products": "Write",
- "read_products": "Read"
- }
- },
- "Publications": {
- "description": "View or manage groups of products that have been published to an app",
- "scopes": {
- "write_publications": "Write",
- "read_publications": "Read"
- }
- },
- "Purchase options": {
- "description": "View or manage purchase options owned by this app",
- "scopes": {
- "write_purchase_options": "Write",
- "read_purchase_options": "Read"
- }
- },
- "Reports": {
- "description": "View or manage reports on the Reports page in the Shopify admin",
- "scopes": {
- "write_reports": "Write",
- "read_reports": "Read"
- }
- },
- "Resource feedback": {
- "description": "View or manage the status of shops and resources",
- "scopes": {
- "write_resource_feedbacks": "Write",
- "read_resource_feedbacks": "Read"
- }
- },
- "Returns": {
- "description": "View or manage returns",
- "scopes": {
- "write_returns": "Write",
- "read_returns": "Read"
- }
- },
- "Sales channels": {
- "description": "View or manage sales channels",
- "scopes": {
- "write_channels": "Write",
- "read_channels": "Read"
- }
- },
- "Script tags": {
- "description": "View or manage the JavaScript code in storefront or orders status pages",
- "scopes": {
- "write_script_tags": "Write",
- "read_script_tags": "Read"
- }
- },
- "Shipping": {
- "description": "View or manage shipping carriers, countries, and provinces",
- "scopes": {
- "write_shipping": "Write",
- "read_shipping": "Read"
- }
- },
- "Shop locales": {
- "description": "View or manage available locales for a shop",
- "scopes": {
- "write_locales": "Write",
- "read_locales": "Read"
- }
- },
- "Shopify Markets": {
- "description": "View or manage Shopify Markets configuration",
- "scopes": {
- "write_markets": "Write",
- "read_markets": "Read"
- }
- },
- "Shopify Payments accounts": {
- "description": "View Shopify Payments accounts",
- "scopes": {
- "read_shopify_payments_accounts": "Read"
- }
- },
- "Shopify Payments bank accounts": {
- "description": "View bank accounts that can receive Shopify Payment payouts",
- "scopes": {
- "read_shopify_payments_bank_accounts": "Read"
- }
- },
- "Shopify Payments disputes": {
- "description": "View Shopify Payment disputes raised by buyers",
- "scopes": {
- "write_shopify_payments_disputes": "Write",
- "read_shopify_payments_disputes": "Read"
- }
- },
- "Shopify Payments payouts": {
- "description": "View Shopify Payments payouts and the account's current balance",
- "scopes": {
- "read_shopify_payments_payouts": "Read"
- }
- },
- "Store content": {
- "description": "View or manage articles, blogs, comments, pages, and redirects",
- "scopes": {
- "write_content": "Write",
- "read_content": "Read"
- }
- },
- "Store credit account transactions": {
- "description": "View or create store credit transactions",
- "scopes": {
- "write_store_credit_account_transactions": "Write",
- "read_store_credit_account_transactions": "Read"
- }
- },
- "Store credit accounts": {
- "description": "View a customer's store credit balance and currency",
- "scopes": {
- "read_store_credit_accounts": "Read"
- }
- },
- "Themes": {
- "description": "View or manage theme templates and assets",
- "scopes": {
- "write_themes": "Write",
- "read_themes": "Read"
- }
- },
- "Third-party fulfillment orders": {
- "description": "View or manage fulfillment orders assigned to a location managed by any fulfillment service",
- "scopes": {
- "write_third_party_fulfillment_orders": "Write",
- "read_third_party_fulfillment_orders": "Read"
- }
- },
- "Translations": {
- "description": "View or manage content that can be translated",
- "scopes": {
- "write_translations": "Write",
- "read_translations": "Read"
- }
- },
- "all_cart_transforms": {
- "description": "",
- "scopes": {
- "read_all_cart_transforms": "Read"
- }
- },
- "all_checkout_completion_target_customizations": {
- "description": "",
- "scopes": {
- "write_all_checkout_completion_target_customizations": "Write",
- "read_all_checkout_completion_target_customizations": "Read"
- }
- },
- "cart_transforms": {
- "description": "",
- "scopes": {
- "write_cart_transforms": "Write",
- "read_cart_transforms": "Read"
- }
- },
- "cash_tracking": {
- "description": "",
- "scopes": {
- "read_cash_tracking": "Read"
- }
- },
- "companies": {
- "description": "",
- "scopes": {
- "write_companies": "Write",
- "read_companies": "Read"
- }
- },
- "custom_fulfillment_services": {
- "description": "",
- "scopes": {
- "write_custom_fulfillment_services": "Write",
- "read_custom_fulfillment_services": "Read"
- }
- },
- "customer_data_erasure": {
- "description": "",
- "scopes": {
- "write_customer_data_erasure": "Write",
- "read_customer_data_erasure": "Read"
- }
- },
- "customer_merge": {
- "description": "",
- "scopes": {
- "write_customer_merge": "Write",
- "read_customer_merge": "Read"
- }
- },
- "delivery_customizations": {
- "description": "",
- "scopes": {
- "write_delivery_customizations": "Write",
- "read_delivery_customizations": "Read"
- }
- },
- "delivery_option_generators": {
- "description": "",
- "scopes": {
- "write_delivery_option_generators": "Write",
- "read_delivery_option_generators": "Read"
- }
- },
- "discounts_allocator_functions": {
- "description": "",
- "scopes": {
- "write_discounts_allocator_functions": "Write",
- "read_discounts_allocator_functions": "Read"
- }
- },
- "fulfillment_constraint_rules": {
- "description": "",
- "scopes": {
- "write_fulfillment_constraint_rules": "Write",
- "read_fulfillment_constraint_rules": "Read"
- }
- },
- "gates": {
- "description": "",
- "scopes": {
- "write_gates": "Write",
- "read_gates": "Read"
- }
- },
- "order_submission_rules": {
- "description": "",
- "scopes": {
- "write_order_submission_rules": "Write",
- "read_order_submission_rules": "Read"
- }
- },
- "privacy_settings": {
- "description": "",
- "scopes": {
- "write_privacy_settings": "Write",
- "read_privacy_settings": "Read"
- }
- },
- "shopify_payments_provider_accounts_sensitive": {
- "description": "",
- "scopes": {
- "read_shopify_payments_provider_accounts_sensitive": "Read"
- }
- },
- "validations": {
- "description": "",
- "scopes": {
- "write_validations": "Write",
- "read_validations": "Read"
- }
- }
- }
-}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/shopify/shopify.go b/pkg/analyzer/analyzers/shopify/shopify.go
deleted file mode 100644
index efd14fd75c0f..000000000000
--- a/pkg/analyzer/analyzers/shopify/shopify.go
+++ /dev/null
@@ -1,329 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go shopify
-
-package shopify
-
-import (
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "os"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-var (
- // order the categories
- categoryOrder = []string{"Analytics", "Applications", "Assigned fulfillment orders", "Browsing behavior", "Custom pixels", "Customers", "Discounts", "Discovery", "Draft orders", "Files", "Fulfillment services", "Gift cards", "Inventory", "Legal policies", "Locations", "Marketing events", "Merchant-managed fulfillment orders", "Metaobject definitions", "Metaobject entries", "Online Store navigation", "Online Store pages", "Order editing", "Orders", "Packing slip management", "Payment customizations", "Payment terms", "Pixels", "Price rules", "Product feeds", "Product listings", "Products", "Publications", "Purchase options", "Reports", "Resource feedback", "Returns", "Sales channels", "Script tags", "Shipping", "Shop locales", "Shopify Markets", "Shopify Payments accounts", "Shopify Payments bank accounts", "Shopify Payments disputes", "Shopify Payments payouts", "Store content", "Store credit account transactions", "Store credit accounts", "Themes", "Third-party fulfillment orders", "Translations", "all_cart_transforms", "all_checkout_completion_target_customizations", "cart_transforms", "cash_tracking", "companies", "custom_fulfillment_services", "customer_data_erasure", "customer_merge", "delivery_customizations", "delivery_option_generators", "discounts_allocator_functions", "fulfillment_constraint_rules", "gates", "order_submission_rules", "privacy_settings", "shopify_payments_provider_accounts_sensitive", "validations"}
-)
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeShopify }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("key not found in credentialInfo")
- }
-
- storeUrl, ok := credInfo["store_url"]
- if !ok {
- return nil, errors.New("store_url not found in credentialInfo")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key, storeUrl)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeShopify,
- Metadata: map[string]any{
- "status_code": info.StatusCode,
- },
- }
-
- resource := &analyzers.Resource{
- Name: info.ShopInfo.Shop.Name,
- FullyQualifiedName: info.ShopInfo.Shop.Domain + "/" + info.ShopInfo.Shop.Email,
- Type: "shop",
- Metadata: map[string]any{
- "created_at": info.ShopInfo.Shop.CreatedAt,
- },
- Parent: nil,
- }
- result.Bindings = make([]analyzers.Binding, 0)
-
- for _, category := range categoryOrder {
- if val, ok := info.Scopes[category]; ok {
- cateogryResource := &analyzers.Resource{
- Name: category,
- FullyQualifiedName: resource.FullyQualifiedName + "/" + category, // shop.domain/shop.email/category
- Type: "category",
- Parent: resource,
- }
-
- if sliceContains(val.Scopes, "Read") && sliceContains(val.Scopes, "Write") {
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: *cateogryResource,
- Permission: analyzers.Permission{
- Value: PermissionStrings[FullAccess],
- },
- })
- continue
- }
-
- for _, scope := range val.Scopes {
- lowerScope := strings.ToLower(scope)
- if _, ok := StringToPermission[lowerScope]; !ok { // skip unknown scopes/permission
- continue
- }
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: *cateogryResource,
- Permission: analyzers.Permission{
- Value: lowerScope,
- },
- })
- }
- }
- }
-
- return &result
-}
-
-//go:embed scopes.json
-var scopesConfig []byte
-
-func sliceContains(slice []string, value string) bool {
- for _, v := range slice {
- if v == value {
- return true
- }
- }
- return false
-}
-
-type OutputScopes struct {
- Description string
- Scopes []string
-}
-
-func (o OutputScopes) PrintScopes() string {
- // Custom rules unique to this analyzer
- var scopes []string
- if sliceContains(o.Scopes, "Read") && sliceContains(o.Scopes, "Write") {
- scopes = append(scopes, "Read & Write")
- for _, scope := range o.Scopes {
- if scope != "Read" && scope != "Write" {
- scopes = append(scopes, scope)
- }
- }
- } else {
- scopes = append(scopes, o.Scopes...)
- }
- return strings.Join(scopes, ", ")
-}
-
-// Category represents the structure of each category in the JSON
-type CategoryJSON struct {
- Description string `json:"description"`
- Scopes map[string]string `json:"scopes"`
-}
-
-// Data represents the overall JSON structure
-type ScopeDataJSON struct {
- Categories map[string]CategoryJSON `json:"categories"`
-}
-
-// Function to determine the appropriate scope
-func determineScopes(data ScopeDataJSON, input string) map[string]OutputScopes {
- // Split the input string into individual scopes
- inputScopes := strings.Split(input, ", ")
-
- // Map to store scopes found for each category
- scopeResults := make(map[string]OutputScopes)
-
- // Populate categoryScopes map with individual scopes found
- for _, scope := range inputScopes {
- for category, catData := range data.Categories {
- if scopeType, exists := catData.Scopes[scope]; exists {
- if _, ok := scopeResults[category]; !ok {
- scopeResults[category] = OutputScopes{Description: catData.Description}
- }
- // Extract the struct from the map
- outputData := scopeResults[category]
-
- // Modify the struct (ex: append "Read" or "Write" to the Scopes slice)
- outputData.Scopes = append(outputData.Scopes, scopeType)
-
- // Reassign the modified struct back to the map
- scopeResults[category] = outputData
- }
- }
- }
-
- return scopeResults
-}
-
-type ShopInfoJSON struct {
- Shop struct {
- Domain string `json:"domain"`
- Name string `json:"name"`
- Email string `json:"email"`
- CreatedAt string `json:"created_at"`
- } `json:"shop"`
-}
-
-type SecretInfo struct {
- StatusCode int
- ShopInfo ShopInfoJSON
- Scopes map[string]OutputScopes
-}
-
-func getShopInfo(cfg *config.Config, key string, store string) (ShopInfoJSON, error) {
- var shopInfo ShopInfoJSON
-
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", fmt.Sprintf("https://%s/admin/api/2024-04/shop.json", store), nil)
- if err != nil {
- return shopInfo, err
- }
-
- req.Header.Set("X-Shopify-Access-Token", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return shopInfo, err
- }
-
- defer resp.Body.Close()
-
- err = json.NewDecoder(resp.Body).Decode(&shopInfo)
- if err != nil {
- return shopInfo, err
- }
- return shopInfo, nil
-}
-
-type AccessScopesJSON struct {
- AccessScopes []struct {
- Handle string `json:"handle"`
- } `json:"access_scopes"`
-}
-
-func (a AccessScopesJSON) String() string {
- var scopes []string
- for _, scope := range a.AccessScopes {
- scopes = append(scopes, scope.Handle)
- }
- return strings.Join(scopes, ", ")
-}
-
-func getAccessScopes(cfg *config.Config, key string, store string) (AccessScopesJSON, int, error) {
- var accessScopes AccessScopesJSON
-
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", fmt.Sprintf("https://%s/admin/oauth/access_scopes.json", store), nil)
- if err != nil {
- return accessScopes, -1, err
- }
-
- req.Header.Set("X-Shopify-Access-Token", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return accessScopes, resp.StatusCode, err
- }
-
- defer resp.Body.Close()
-
- err = json.NewDecoder(resp.Body).Decode(&accessScopes)
- if err != nil {
- return accessScopes, resp.StatusCode, err
- }
- return accessScopes, resp.StatusCode, nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string, storeURL string) {
- // ToDo: Add in logging
- if cfg.LoggingEnabled {
- color.Red("[x] Logging is not supported for this analyzer.")
- return
- }
-
- info, err := AnalyzePermissions(cfg, key, storeURL)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
-
- if info.StatusCode != 200 {
- color.Red("[x] Invalid Shopfiy API Key and Store URL combination")
- return
- }
- color.Green("[i] Valid Shopify API Key\n\n")
-
- color.Yellow("[i] Shop Information\n")
- color.Yellow("Name: %s", info.ShopInfo.Shop.Name)
- color.Yellow("Email: %s", info.ShopInfo.Shop.Email)
- color.Yellow("Created At: %s\n\n", info.ShopInfo.Shop.CreatedAt)
-
- printAccessScopes(info.Scopes)
-}
-
-func AnalyzePermissions(cfg *config.Config, key string, storeURL string) (*SecretInfo, error) {
-
- accessScopes, statusCode, err := getAccessScopes(cfg, key, storeURL)
- if err != nil {
- return nil, err
- }
-
- shopInfo, err := getShopInfo(cfg, key, storeURL)
- if err != nil {
- return nil, err
- }
-
- var data ScopeDataJSON
- if err := json.Unmarshal(scopesConfig, &data); err != nil {
- return nil, err
- }
- scopes := determineScopes(data, accessScopes.String())
-
- return &SecretInfo{
- StatusCode: statusCode,
- ShopInfo: shopInfo,
- Scopes: scopes,
- }, nil
-}
-
-func printAccessScopes(accessScopes map[string]OutputScopes) {
- color.Yellow("[i] Access Scopes\n")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Scope", "Description", "Access"})
-
- for _, category := range categoryOrder {
- if val, ok := accessScopes[category]; ok {
- t.AppendRow([]interface{}{color.GreenString(category), color.GreenString(val.Description), color.GreenString(val.PrintScopes())})
- }
- }
- t.Render()
-
-}
diff --git a/pkg/analyzer/analyzers/shopify/shopify_test.go b/pkg/analyzer/analyzers/shopify/shopify_test.go
deleted file mode 100644
index b0f58b9239a8..000000000000
--- a/pkg/analyzer/analyzers/shopify/shopify_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package shopify
-
-import (
- _ "embed"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- secret := testSecrets.MustGetField("SHOPIFY_ADMIN_SECRET")
- domain := testSecrets.MustGetField("SHOPIFY_DOMAIN")
-
- tests := []struct {
- name string
- key string
- storeUrl string
- want string
- wantErr bool
- }{
- {
- name: "valid Shopify key",
- key: secret,
- storeUrl: domain,
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key, "store_url": tt.storeUrl})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/slack/expected_output.json b/pkg/analyzer/analyzers/slack/expected_output.json
deleted file mode 100644
index ae967361132a..000000000000
--- a/pkg/analyzer/analyzers/slack/expected_output.json
+++ /dev/null
@@ -1,667 +0,0 @@
-{
- "AnalyzerType": 16,
- "Bindings": [
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "conversations.history",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "conversations.replies",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "channels.info",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "conversations.info",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "conversations.list",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "conversations.members",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "groups.info",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "im.list",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "mpim.list",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "users.conversations",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "emoji.list",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "files.info",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "files.list",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "stars.list",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "pins.list",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "usergroups.list",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "usergroups.users.list",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "dnd.info",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "dnd.teamInfo",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "marge.haskell.bridge",
- "FullyQualifiedName": "TSMCXP5FH/USMD5JM0F",
- "Type": "user",
- "Metadata": {
- "scopes": [
- "identify",
- "channels:history",
- "groups:history",
- "im:history",
- "channels:read",
- "emoji:read",
- "files:read",
- "groups:read",
- "im:read",
- "stars:read",
- "pins:read",
- "usergroups:read",
- "dnd:read",
- "calls:read"
- ],
- "team": "ct.org",
- "team_id": "TSMCXP5FH",
- "url": "https://ctorgworkspace.slack.com/"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "calls.info",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": null
- }
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/slack/permissions.go b/pkg/analyzer/analyzers/slack/permissions.go
deleted file mode 100644
index 1f98e8a5e9dc..000000000000
--- a/pkg/analyzer/analyzers/slack/permissions.go
+++ /dev/null
@@ -1,1711 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package slack
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- AdminAnalyticsRead Permission = iota
- AdminAnalyticsGetfile Permission = iota
- AdminAppActivitiesRead Permission = iota
- AdminAppsActivitiesList Permission = iota
- AdminAppsWrite Permission = iota
- AdminAppsApprove Permission = iota
- AdminAppsClearresolution Permission = iota
- AdminAppsConfigSet Permission = iota
- AdminAppsRequestsCancel Permission = iota
- AdminAppsRestrict Permission = iota
- AdminAppsUninstall Permission = iota
- AdminAppsRead Permission = iota
- AdminAppsApprovedList Permission = iota
- AdminAppsConfigLookup Permission = iota
- AdminAppsRequestsList Permission = iota
- AdminAppsRestrictedList Permission = iota
- AdminUsersWrite Permission = iota
- AdminAuthPolicyAssignentities Permission = iota
- AdminAuthPolicyRemoveentities Permission = iota
- AdminUsersAssign Permission = iota
- AdminUsersInvite Permission = iota
- AdminUsersRemove Permission = iota
- AdminUsersSessionClearsettings Permission = iota
- AdminUsersSessionInvalidate Permission = iota
- AdminUsersSessionReset Permission = iota
- AdminUsersSessionResetbulk Permission = iota
- AdminUsersSessionSetsettings Permission = iota
- AdminUsersSetadmin Permission = iota
- AdminUsersSetexpiration Permission = iota
- AdminUsersSetowner Permission = iota
- AdminUsersSetregular Permission = iota
- AdminUsersRead Permission = iota
- AdminAuthPolicyGetentities Permission = iota
- AdminUsersList Permission = iota
- AdminUsersSessionGetsettings Permission = iota
- AdminUsersSessionList Permission = iota
- AdminUsersUnsupportedversionsExport Permission = iota
- AdminBarriersWrite Permission = iota
- AdminBarriersCreate Permission = iota
- AdminBarriersDelete Permission = iota
- AdminBarriersUpdate Permission = iota
- AdminBarriersRead Permission = iota
- AdminBarriersList Permission = iota
- AdminConversationsWrite Permission = iota
- AdminConversationsArchive Permission = iota
- AdminConversationsBulkarchive Permission = iota
- AdminConversationsBulkdelete Permission = iota
- AdminConversationsBulkmove Permission = iota
- AdminConversationsConverttoprivate Permission = iota
- AdminConversationsConverttopublic Permission = iota
- AdminConversationsCreate Permission = iota
- AdminConversationsDelete Permission = iota
- AdminConversationsDisconnectshared Permission = iota
- AdminConversationsInvite Permission = iota
- AdminConversationsRemovecustomretention Permission = iota
- AdminConversationsRename Permission = iota
- AdminConversationsRestrictaccessAddgroup Permission = iota
- AdminConversationsRestrictaccessRemovegroup Permission = iota
- AdminConversationsSetconversationprefs Permission = iota
- AdminConversationsSetcustomretention Permission = iota
- AdminConversationsSetteams Permission = iota
- AdminConversationsUnarchive Permission = iota
- AdminConversationsRead Permission = iota
- AdminConversationsEkmListoriginalconnectedchannelinfo Permission = iota
- AdminConversationsGetconversationprefs Permission = iota
- AdminConversationsGetcustomretention Permission = iota
- AdminConversationsGetteams Permission = iota
- AdminConversationsLookup Permission = iota
- AdminConversationsRestrictaccessListgroups Permission = iota
- AdminConversationsSearch Permission = iota
- AdminTeamsWrite Permission = iota
- AdminEmojiAdd Permission = iota
- AdminEmojiAddalias Permission = iota
- AdminEmojiRemove Permission = iota
- AdminTeamsCreate Permission = iota
- AdminTeamsSettingsSetdefaultchannels Permission = iota
- AdminTeamsSettingsSetdescription Permission = iota
- AdminTeamsSettingsSetdiscoverability Permission = iota
- AdminTeamsSettingsSeticon Permission = iota
- AdminTeamsSettingsSetname Permission = iota
- AdminUsergroupsAddteams Permission = iota
- AdminTeamsRead Permission = iota
- AdminEmojiList Permission = iota
- AdminTeamsAdminsList Permission = iota
- AdminTeamsList Permission = iota
- AdminTeamsOwnersList Permission = iota
- AdminTeamsSettingsInfo Permission = iota
- AdminWorkflowsRead Permission = iota
- AdminFunctionsList Permission = iota
- AdminFunctionsPermissionsLookup Permission = iota
- AdminWorkflowsPermissionsLookup Permission = iota
- AdminWorkflowsSearch Permission = iota
- AdminWorkflowsWrite Permission = iota
- AdminFunctionsPermissionsSet Permission = iota
- AdminWorkflowsCollaboratorsAdd Permission = iota
- AdminWorkflowsCollaboratorsRemove Permission = iota
- AdminWorkflowsUnpublish Permission = iota
- AdminInvitesWrite Permission = iota
- AdminInviterequestsApprove Permission = iota
- AdminInviterequestsDeny Permission = iota
- AdminInvitesRead Permission = iota
- AdminInviterequestsApprovedList Permission = iota
- AdminInviterequestsDeniedList Permission = iota
- AdminInviterequestsList Permission = iota
- AdminRolesWrite Permission = iota
- AdminRolesAddassignments Permission = iota
- AdminRolesRemoveassignments Permission = iota
- AdminRolesRead Permission = iota
- AdminRolesListassignments Permission = iota
- AdminUsergroupsWrite Permission = iota
- AdminUsergroupsAddchannels Permission = iota
- AdminUsergroupsRemovechannels Permission = iota
- AdminUsergroupsRead Permission = iota
- AdminUsergroupsListchannels Permission = iota
- HostingRead Permission = iota
- AppsActivitiesList Permission = iota
- ConnectionsWrite Permission = iota
- AppsConnectionsOpen Permission = iota
- Token Permission = iota
- AppsDatastoreBulkdelete Permission = iota
- AppsDatastoreBulkget Permission = iota
- AppsDatastoreBulkput Permission = iota
- AppsDatastoreDelete Permission = iota
- AppsDatastoreGet Permission = iota
- AppsDatastorePut Permission = iota
- AppsDatastoreQuery Permission = iota
- AppsDatastoreUpdate Permission = iota
- DatastoreRead Permission = iota
- AppsDatastoreCount Permission = iota
- AuthorizationsRead Permission = iota
- AppsEventAuthorizationsList Permission = iota
- Bot Permission = iota
- AuthRevoke Permission = iota
- AuthTest Permission = iota
- ChatGetpermalink Permission = iota
- ChatScheduledmessagesList Permission = iota
- DialogOpen Permission = iota
- FunctionsCompleteerror Permission = iota
- FunctionsCompletesuccess Permission = iota
- RtmConnect Permission = iota
- RtmStart Permission = iota
- ViewsOpen Permission = iota
- ViewsPublish Permission = iota
- ViewsPush Permission = iota
- ViewsUpdate Permission = iota
- BookmarksWrite Permission = iota
- BookmarksAdd Permission = iota
- BookmarksEdit Permission = iota
- BookmarksRemove Permission = iota
- BookmarksRead Permission = iota
- BookmarksList Permission = iota
- UsersRead Permission = iota
- BotsInfo Permission = iota
- UsersGetpresence Permission = iota
- UsersInfo Permission = iota
- UsersList Permission = iota
- CallsWrite Permission = iota
- CallsAdd Permission = iota
- CallsEnd Permission = iota
- CallsParticipantsAdd Permission = iota
- CallsParticipantsRemove Permission = iota
- CallsUpdate Permission = iota
- CallsRead Permission = iota
- CallsInfo Permission = iota
- ChannelsManage Permission = iota
- ChannelsCreate Permission = iota
- ChannelsMark Permission = iota
- ConversationsArchive Permission = iota
- ConversationsClose Permission = iota
- ConversationsCreate Permission = iota
- ConversationsKick Permission = iota
- ConversationsLeave Permission = iota
- ConversationsMark Permission = iota
- ConversationsOpen Permission = iota
- ConversationsRename Permission = iota
- ConversationsUnarchive Permission = iota
- GroupsCreate Permission = iota
- GroupsMark Permission = iota
- ImMark Permission = iota
- ImOpen Permission = iota
- MpimMark Permission = iota
- MpimOpen Permission = iota
- ChannelsRead Permission = iota
- ChannelsInfo Permission = iota
- ConversationsInfo Permission = iota
- ConversationsList Permission = iota
- ConversationsMembers Permission = iota
- GroupsInfo Permission = iota
- ImList Permission = iota
- MpimList Permission = iota
- UsersConversations Permission = iota
- ChannelsWriteInvites Permission = iota
- ChannelsInvite Permission = iota
- ConversationsInvite Permission = iota
- GroupsInvite Permission = iota
- ChatWrite Permission = iota
- ChatDelete Permission = iota
- ChatDeletescheduledmessage Permission = iota
- ChatMemessage Permission = iota
- ChatPostephemeral Permission = iota
- ChatPostmessage Permission = iota
- ChatSchedulemessage Permission = iota
- ChatUpdate Permission = iota
- LinksWrite Permission = iota
- ChatUnfurl Permission = iota
- ConversationsConnectWrite Permission = iota
- ConversationsAcceptsharedinvite Permission = iota
- ConversationsInviteshared Permission = iota
- ConversationsConnectManage Permission = iota
- ConversationsApprovesharedinvite Permission = iota
- ConversationsDeclinesharedinvite Permission = iota
- ConversationsListconnectinvites Permission = iota
- ChannelsHistory Permission = iota
- ConversationsHistory Permission = iota
- ConversationsReplies Permission = iota
- ChannelsJoin Permission = iota
- ConversationsJoin Permission = iota
- ChannelsWriteTopic Permission = iota
- ConversationsSetpurpose Permission = iota
- ConversationsSettopic Permission = iota
- DndWrite Permission = iota
- DndEnddnd Permission = iota
- DndEndsnooze Permission = iota
- DndSetsnooze Permission = iota
- DndRead Permission = iota
- DndInfo Permission = iota
- DndTeaminfo Permission = iota
- EmojiRead Permission = iota
- EmojiList Permission = iota
- FilesWrite Permission = iota
- FilesCommentsDelete Permission = iota
- FilesCompleteuploadexternal Permission = iota
- FilesDelete Permission = iota
- FilesGetuploadurlexternal Permission = iota
- FilesRevokepublicurl Permission = iota
- FilesSharedpublicurl Permission = iota
- FilesUpload Permission = iota
- FilesRead Permission = iota
- FilesInfo Permission = iota
- FilesList Permission = iota
- RemoteFilesWrite Permission = iota
- FilesRemoteAdd Permission = iota
- FilesRemoteRemove Permission = iota
- FilesRemoteUpdate Permission = iota
- RemoteFilesRead Permission = iota
- FilesRemoteInfo Permission = iota
- FilesRemoteList Permission = iota
- RemoteFilesShare Permission = iota
- FilesRemoteShare Permission = iota
- AppConfigurationsWrite Permission = iota
- FunctionsDistributionsPermissionsAdd Permission = iota
- FunctionsDistributionsPermissionsRemove Permission = iota
- FunctionsDistributionsPermissionsSet Permission = iota
- AppConfigurationsRead Permission = iota
- FunctionsDistributionsPermissionsList Permission = iota
- Conversations Permission = iota
- GroupsOpen Permission = iota
- TokensBasic Permission = iota
- MigrationExchange Permission = iota
- Email Permission = iota
- OpenidConnectUserinfo Permission = iota
- PinsWrite Permission = iota
- PinsAdd Permission = iota
- PinsRemove Permission = iota
- PinsRead Permission = iota
- PinsList Permission = iota
- ReactionsWrite Permission = iota
- ReactionsAdd Permission = iota
- ReactionsRemove Permission = iota
- ReactionsRead Permission = iota
- ReactionsGet Permission = iota
- ReactionsList Permission = iota
- RemindersWrite Permission = iota
- RemindersAdd Permission = iota
- RemindersComplete Permission = iota
- RemindersDelete Permission = iota
- RemindersRead Permission = iota
- RemindersInfo Permission = iota
- RemindersList Permission = iota
- SearchRead Permission = iota
- SearchAll Permission = iota
- SearchFiles Permission = iota
- SearchMessages Permission = iota
- StarsWrite Permission = iota
- StarsAdd Permission = iota
- StarsRemove Permission = iota
- StarsRead Permission = iota
- StarsList Permission = iota
- Admin Permission = iota
- TeamAccesslogs Permission = iota
- TeamBillableinfo Permission = iota
- TeamIntegrationlogs Permission = iota
- TeamBillingRead Permission = iota
- TeamBillingInfo Permission = iota
- TeamRead Permission = iota
- TeamInfo Permission = iota
- TeamPreferencesRead Permission = iota
- TeamPreferencesList Permission = iota
- UsersProfileRead Permission = iota
- TeamProfileGet Permission = iota
- UsersProfileGet Permission = iota
- UsergroupsWrite Permission = iota
- UsergroupsCreate Permission = iota
- UsergroupsDisable Permission = iota
- UsergroupsEnable Permission = iota
- UsergroupsUpdate Permission = iota
- UsergroupsUsersUpdate Permission = iota
- UsergroupsRead Permission = iota
- UsergroupsList Permission = iota
- UsergroupsUsersList Permission = iota
- UsersProfileWrite Permission = iota
- UsersDeletephoto Permission = iota
- UsersProfileSet Permission = iota
- UsersSetphoto Permission = iota
- IdentityBasic Permission = iota
- UsersIdentity Permission = iota
- UsersReadEmail Permission = iota
- UsersLookupbyemail Permission = iota
- UsersWrite Permission = iota
- UsersSetactive Permission = iota
- UsersSetpresence Permission = iota
- WorkflowStepsExecute Permission = iota
- WorkflowsStepcompleted Permission = iota
- WorkflowsStepfailed Permission = iota
- WorkflowsUpdatestep Permission = iota
- TriggersWrite Permission = iota
- WorkflowsTriggersPermissionsAdd Permission = iota
- WorkflowsTriggersPermissionsRemove Permission = iota
- WorkflowsTriggersPermissionsSet Permission = iota
- TriggersRead Permission = iota
- WorkflowsTriggersPermissionsList Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- AdminAnalyticsRead: "admin.analytics:read",
- AdminAnalyticsGetfile: "admin.analytics.getFile",
- AdminAppActivitiesRead: "admin.app_activities:read",
- AdminAppsActivitiesList: "admin.apps.activities.list",
- AdminAppsWrite: "admin.apps:write",
- AdminAppsApprove: "admin.apps.approve",
- AdminAppsClearresolution: "admin.apps.clearResolution",
- AdminAppsConfigSet: "admin.apps.config.set",
- AdminAppsRequestsCancel: "admin.apps.requests.cancel",
- AdminAppsRestrict: "admin.apps.restrict",
- AdminAppsUninstall: "admin.apps.uninstall",
- AdminAppsRead: "admin.apps:read",
- AdminAppsApprovedList: "admin.apps.approved.list",
- AdminAppsConfigLookup: "admin.apps.config.lookup",
- AdminAppsRequestsList: "admin.apps.requests.list",
- AdminAppsRestrictedList: "admin.apps.restricted.list",
- AdminUsersWrite: "admin.users:write",
- AdminAuthPolicyAssignentities: "admin.auth.policy.assignEntities",
- AdminAuthPolicyRemoveentities: "admin.auth.policy.removeEntities",
- AdminUsersAssign: "admin.users.assign",
- AdminUsersInvite: "admin.users.invite",
- AdminUsersRemove: "admin.users.remove",
- AdminUsersSessionClearsettings: "admin.users.session.clearSettings",
- AdminUsersSessionInvalidate: "admin.users.session.invalidate",
- AdminUsersSessionReset: "admin.users.session.reset",
- AdminUsersSessionResetbulk: "admin.users.session.resetBulk",
- AdminUsersSessionSetsettings: "admin.users.session.setSettings",
- AdminUsersSetadmin: "admin.users.setAdmin",
- AdminUsersSetexpiration: "admin.users.setExpiration",
- AdminUsersSetowner: "admin.users.setOwner",
- AdminUsersSetregular: "admin.users.setRegular",
- AdminUsersRead: "admin.users:read",
- AdminAuthPolicyGetentities: "admin.auth.policy.getEntities",
- AdminUsersList: "admin.users.list",
- AdminUsersSessionGetsettings: "admin.users.session.getSettings",
- AdminUsersSessionList: "admin.users.session.list",
- AdminUsersUnsupportedversionsExport: "admin.users.unsupportedVersions.export",
- AdminBarriersWrite: "admin.barriers:write",
- AdminBarriersCreate: "admin.barriers.create",
- AdminBarriersDelete: "admin.barriers.delete",
- AdminBarriersUpdate: "admin.barriers.update",
- AdminBarriersRead: "admin.barriers:read",
- AdminBarriersList: "admin.barriers.list",
- AdminConversationsWrite: "admin.conversations:write",
- AdminConversationsArchive: "admin.conversations.archive",
- AdminConversationsBulkarchive: "admin.conversations.bulkArchive",
- AdminConversationsBulkdelete: "admin.conversations.bulkDelete",
- AdminConversationsBulkmove: "admin.conversations.bulkMove",
- AdminConversationsConverttoprivate: "admin.conversations.convertToPrivate",
- AdminConversationsConverttopublic: "admin.conversations.convertToPublic",
- AdminConversationsCreate: "admin.conversations.create",
- AdminConversationsDelete: "admin.conversations.delete",
- AdminConversationsDisconnectshared: "admin.conversations.disconnectShared",
- AdminConversationsInvite: "admin.conversations.invite",
- AdminConversationsRemovecustomretention: "admin.conversations.removeCustomRetention",
- AdminConversationsRename: "admin.conversations.rename",
- AdminConversationsRestrictaccessAddgroup: "admin.conversations.restrictAccess.addGroup",
- AdminConversationsRestrictaccessRemovegroup: "admin.conversations.restrictAccess.removeGroup",
- AdminConversationsSetconversationprefs: "admin.conversations.setConversationPrefs",
- AdminConversationsSetcustomretention: "admin.conversations.setCustomRetention",
- AdminConversationsSetteams: "admin.conversations.setTeams",
- AdminConversationsUnarchive: "admin.conversations.unarchive",
- AdminConversationsRead: "admin.conversations:read",
- AdminConversationsEkmListoriginalconnectedchannelinfo: "admin.conversations.ekm.listOriginalConnectedChannelInfo",
- AdminConversationsGetconversationprefs: "admin.conversations.getConversationPrefs",
- AdminConversationsGetcustomretention: "admin.conversations.getCustomRetention",
- AdminConversationsGetteams: "admin.conversations.getTeams",
- AdminConversationsLookup: "admin.conversations.lookup",
- AdminConversationsRestrictaccessListgroups: "admin.conversations.restrictAccess.listGroups",
- AdminConversationsSearch: "admin.conversations.search",
- AdminTeamsWrite: "admin.teams:write",
- AdminEmojiAdd: "admin.emoji.add",
- AdminEmojiAddalias: "admin.emoji.addAlias",
- AdminEmojiRemove: "admin.emoji.remove",
- AdminTeamsCreate: "admin.teams.create",
- AdminTeamsSettingsSetdefaultchannels: "admin.teams.settings.setDefaultChannels",
- AdminTeamsSettingsSetdescription: "admin.teams.settings.setDescription",
- AdminTeamsSettingsSetdiscoverability: "admin.teams.settings.setDiscoverability",
- AdminTeamsSettingsSeticon: "admin.teams.settings.setIcon",
- AdminTeamsSettingsSetname: "admin.teams.settings.setName",
- AdminUsergroupsAddteams: "admin.usergroups.addTeams",
- AdminTeamsRead: "admin.teams:read",
- AdminEmojiList: "admin.emoji.list",
- AdminTeamsAdminsList: "admin.teams.admins.list",
- AdminTeamsList: "admin.teams.list",
- AdminTeamsOwnersList: "admin.teams.owners.list",
- AdminTeamsSettingsInfo: "admin.teams.settings.info",
- AdminWorkflowsRead: "admin.workflows:read",
- AdminFunctionsList: "admin.functions.list",
- AdminFunctionsPermissionsLookup: "admin.functions.permissions.lookup",
- AdminWorkflowsPermissionsLookup: "admin.workflows.permissions.lookup",
- AdminWorkflowsSearch: "admin.workflows.search",
- AdminWorkflowsWrite: "admin.workflows:write",
- AdminFunctionsPermissionsSet: "admin.functions.permissions.set",
- AdminWorkflowsCollaboratorsAdd: "admin.workflows.collaborators.add",
- AdminWorkflowsCollaboratorsRemove: "admin.workflows.collaborators.remove",
- AdminWorkflowsUnpublish: "admin.workflows.unpublish",
- AdminInvitesWrite: "admin.invites:write",
- AdminInviterequestsApprove: "admin.inviteRequests.approve",
- AdminInviterequestsDeny: "admin.inviteRequests.deny",
- AdminInvitesRead: "admin.invites:read",
- AdminInviterequestsApprovedList: "admin.inviteRequests.approved.list",
- AdminInviterequestsDeniedList: "admin.inviteRequests.denied.list",
- AdminInviterequestsList: "admin.inviteRequests.list",
- AdminRolesWrite: "admin.roles:write",
- AdminRolesAddassignments: "admin.roles.addAssignments",
- AdminRolesRemoveassignments: "admin.roles.removeAssignments",
- AdminRolesRead: "admin.roles:read",
- AdminRolesListassignments: "admin.roles.listAssignments",
- AdminUsergroupsWrite: "admin.usergroups:write",
- AdminUsergroupsAddchannels: "admin.usergroups.addChannels",
- AdminUsergroupsRemovechannels: "admin.usergroups.removeChannels",
- AdminUsergroupsRead: "admin.usergroups:read",
- AdminUsergroupsListchannels: "admin.usergroups.listChannels",
- HostingRead: "hosting:read",
- AppsActivitiesList: "apps.activities.list",
- ConnectionsWrite: "connections:write",
- AppsConnectionsOpen: "apps.connections.open",
- Token: "token",
- AppsDatastoreBulkdelete: "apps.datastore.bulkDelete",
- AppsDatastoreBulkget: "apps.datastore.bulkGet",
- AppsDatastoreBulkput: "apps.datastore.bulkPut",
- AppsDatastoreDelete: "apps.datastore.delete",
- AppsDatastoreGet: "apps.datastore.get",
- AppsDatastorePut: "apps.datastore.put",
- AppsDatastoreQuery: "apps.datastore.query",
- AppsDatastoreUpdate: "apps.datastore.update",
- DatastoreRead: "datastore:read",
- AppsDatastoreCount: "apps.datastore.count",
- AuthorizationsRead: "authorizations:read",
- AppsEventAuthorizationsList: "apps.event.authorizations.list",
- Bot: "bot",
- AuthRevoke: "auth.revoke",
- AuthTest: "auth.test",
- ChatGetpermalink: "chat.getPermalink",
- ChatScheduledmessagesList: "chat.scheduledMessages.list",
- DialogOpen: "dialog.open",
- FunctionsCompleteerror: "functions.completeError",
- FunctionsCompletesuccess: "functions.completeSuccess",
- RtmConnect: "rtm.connect",
- RtmStart: "rtm.start",
- ViewsOpen: "views.open",
- ViewsPublish: "views.publish",
- ViewsPush: "views.push",
- ViewsUpdate: "views.update",
- BookmarksWrite: "bookmarks:write",
- BookmarksAdd: "bookmarks.add",
- BookmarksEdit: "bookmarks.edit",
- BookmarksRemove: "bookmarks.remove",
- BookmarksRead: "bookmarks:read",
- BookmarksList: "bookmarks.list",
- UsersRead: "users:read",
- BotsInfo: "bots.info",
- UsersGetpresence: "users.getPresence",
- UsersInfo: "users.info",
- UsersList: "users.list",
- CallsWrite: "calls:write",
- CallsAdd: "calls.add",
- CallsEnd: "calls.end",
- CallsParticipantsAdd: "calls.participants.add",
- CallsParticipantsRemove: "calls.participants.remove",
- CallsUpdate: "calls.update",
- CallsRead: "calls:read",
- CallsInfo: "calls.info",
- ChannelsManage: "channels:manage",
- ChannelsCreate: "channels.create",
- ChannelsMark: "channels.mark",
- ConversationsArchive: "conversations.archive",
- ConversationsClose: "conversations.close",
- ConversationsCreate: "conversations.create",
- ConversationsKick: "conversations.kick",
- ConversationsLeave: "conversations.leave",
- ConversationsMark: "conversations.mark",
- ConversationsOpen: "conversations.open",
- ConversationsRename: "conversations.rename",
- ConversationsUnarchive: "conversations.unarchive",
- GroupsCreate: "groups.create",
- GroupsMark: "groups.mark",
- ImMark: "im.mark",
- ImOpen: "im.open",
- MpimMark: "mpim.mark",
- MpimOpen: "mpim.open",
- ChannelsRead: "channels:read",
- ChannelsInfo: "channels.info",
- ConversationsInfo: "conversations.info",
- ConversationsList: "conversations.list",
- ConversationsMembers: "conversations.members",
- GroupsInfo: "groups.info",
- ImList: "im.list",
- MpimList: "mpim.list",
- UsersConversations: "users.conversations",
- ChannelsWriteInvites: "channels:write.invites",
- ChannelsInvite: "channels.invite",
- ConversationsInvite: "conversations.invite",
- GroupsInvite: "groups.invite",
- ChatWrite: "chat:write",
- ChatDelete: "chat.delete",
- ChatDeletescheduledmessage: "chat.deleteScheduledMessage",
- ChatMemessage: "chat.meMessage",
- ChatPostephemeral: "chat.postEphemeral",
- ChatPostmessage: "chat.postMessage",
- ChatSchedulemessage: "chat.scheduleMessage",
- ChatUpdate: "chat.update",
- LinksWrite: "links:write",
- ChatUnfurl: "chat.unfurl",
- ConversationsConnectWrite: "conversations.connect:write",
- ConversationsAcceptsharedinvite: "conversations.acceptSharedInvite",
- ConversationsInviteshared: "conversations.inviteShared",
- ConversationsConnectManage: "conversations.connect:manage",
- ConversationsApprovesharedinvite: "conversations.approveSharedInvite",
- ConversationsDeclinesharedinvite: "conversations.declineSharedInvite",
- ConversationsListconnectinvites: "conversations.listConnectInvites",
- ChannelsHistory: "channels:history",
- ConversationsHistory: "conversations.history",
- ConversationsReplies: "conversations.replies",
- ChannelsJoin: "channels:join",
- ConversationsJoin: "conversations.join",
- ChannelsWriteTopic: "channels:write.topic",
- ConversationsSetpurpose: "conversations.setPurpose",
- ConversationsSettopic: "conversations.setTopic",
- DndWrite: "dnd:write",
- DndEnddnd: "dnd.endDnd",
- DndEndsnooze: "dnd.endSnooze",
- DndSetsnooze: "dnd.setSnooze",
- DndRead: "dnd:read",
- DndInfo: "dnd.info",
- DndTeaminfo: "dnd.teamInfo",
- EmojiRead: "emoji:read",
- EmojiList: "emoji.list",
- FilesWrite: "files:write",
- FilesCommentsDelete: "files.comments.delete",
- FilesCompleteuploadexternal: "files.completeUploadExternal",
- FilesDelete: "files.delete",
- FilesGetuploadurlexternal: "files.getUploadURLExternal",
- FilesRevokepublicurl: "files.revokePublicURL",
- FilesSharedpublicurl: "files.sharedPublicURL",
- FilesUpload: "files.upload",
- FilesRead: "files:read",
- FilesInfo: "files.info",
- FilesList: "files.list",
- RemoteFilesWrite: "remote_files:write",
- FilesRemoteAdd: "files.remote.add",
- FilesRemoteRemove: "files.remote.remove",
- FilesRemoteUpdate: "files.remote.update",
- RemoteFilesRead: "remote_files:read",
- FilesRemoteInfo: "files.remote.info",
- FilesRemoteList: "files.remote.list",
- RemoteFilesShare: "remote_files:share",
- FilesRemoteShare: "files.remote.share",
- AppConfigurationsWrite: "app_configurations:write",
- FunctionsDistributionsPermissionsAdd: "functions.distributions.permissions.add",
- FunctionsDistributionsPermissionsRemove: "functions.distributions.permissions.remove",
- FunctionsDistributionsPermissionsSet: "functions.distributions.permissions.set",
- AppConfigurationsRead: "app_configurations:read",
- FunctionsDistributionsPermissionsList: "functions.distributions.permissions.list",
- Conversations: "conversations",
- GroupsOpen: "groups.open",
- TokensBasic: "tokens.basic",
- MigrationExchange: "migration.exchange",
- Email: "email",
- OpenidConnectUserinfo: "openid.connect.userInfo",
- PinsWrite: "pins:write",
- PinsAdd: "pins.add",
- PinsRemove: "pins.remove",
- PinsRead: "pins:read",
- PinsList: "pins.list",
- ReactionsWrite: "reactions:write",
- ReactionsAdd: "reactions.add",
- ReactionsRemove: "reactions.remove",
- ReactionsRead: "reactions:read",
- ReactionsGet: "reactions.get",
- ReactionsList: "reactions.list",
- RemindersWrite: "reminders:write",
- RemindersAdd: "reminders.add",
- RemindersComplete: "reminders.complete",
- RemindersDelete: "reminders.delete",
- RemindersRead: "reminders:read",
- RemindersInfo: "reminders.info",
- RemindersList: "reminders.list",
- SearchRead: "search:read",
- SearchAll: "search.all",
- SearchFiles: "search.files",
- SearchMessages: "search.messages",
- StarsWrite: "stars:write",
- StarsAdd: "stars.add",
- StarsRemove: "stars.remove",
- StarsRead: "stars:read",
- StarsList: "stars.list",
- Admin: "admin",
- TeamAccesslogs: "team.accessLogs",
- TeamBillableinfo: "team.billableInfo",
- TeamIntegrationlogs: "team.integrationLogs",
- TeamBillingRead: "team.billing:read",
- TeamBillingInfo: "team.billing.info",
- TeamRead: "team:read",
- TeamInfo: "team.info",
- TeamPreferencesRead: "team.preferences:read",
- TeamPreferencesList: "team.preferences.list",
- UsersProfileRead: "users.profile:read",
- TeamProfileGet: "team.profile.get",
- UsersProfileGet: "users.profile.get",
- UsergroupsWrite: "usergroups:write",
- UsergroupsCreate: "usergroups.create",
- UsergroupsDisable: "usergroups.disable",
- UsergroupsEnable: "usergroups.enable",
- UsergroupsUpdate: "usergroups.update",
- UsergroupsUsersUpdate: "usergroups.users.update",
- UsergroupsRead: "usergroups:read",
- UsergroupsList: "usergroups.list",
- UsergroupsUsersList: "usergroups.users.list",
- UsersProfileWrite: "users.profile:write",
- UsersDeletephoto: "users.deletePhoto",
- UsersProfileSet: "users.profile.set",
- UsersSetphoto: "users.setPhoto",
- IdentityBasic: "identity.basic",
- UsersIdentity: "users.identity",
- UsersReadEmail: "users:read.email",
- UsersLookupbyemail: "users.lookupByEmail",
- UsersWrite: "users:write",
- UsersSetactive: "users.setActive",
- UsersSetpresence: "users.setPresence",
- WorkflowStepsExecute: "workflow.steps:execute",
- WorkflowsStepcompleted: "workflows.stepCompleted",
- WorkflowsStepfailed: "workflows.stepFailed",
- WorkflowsUpdatestep: "workflows.updateStep",
- TriggersWrite: "triggers:write",
- WorkflowsTriggersPermissionsAdd: "workflows.triggers.permissions.add",
- WorkflowsTriggersPermissionsRemove: "workflows.triggers.permissions.remove",
- WorkflowsTriggersPermissionsSet: "workflows.triggers.permissions.set",
- TriggersRead: "triggers:read",
- WorkflowsTriggersPermissionsList: "workflows.triggers.permissions.list",
- }
-
- StringToPermission = map[string]Permission{
- "admin.analytics:read": AdminAnalyticsRead,
- "admin.analytics.getFile": AdminAnalyticsGetfile,
- "admin.app_activities:read": AdminAppActivitiesRead,
- "admin.apps.activities.list": AdminAppsActivitiesList,
- "admin.apps:write": AdminAppsWrite,
- "admin.apps.approve": AdminAppsApprove,
- "admin.apps.clearResolution": AdminAppsClearresolution,
- "admin.apps.config.set": AdminAppsConfigSet,
- "admin.apps.requests.cancel": AdminAppsRequestsCancel,
- "admin.apps.restrict": AdminAppsRestrict,
- "admin.apps.uninstall": AdminAppsUninstall,
- "admin.apps:read": AdminAppsRead,
- "admin.apps.approved.list": AdminAppsApprovedList,
- "admin.apps.config.lookup": AdminAppsConfigLookup,
- "admin.apps.requests.list": AdminAppsRequestsList,
- "admin.apps.restricted.list": AdminAppsRestrictedList,
- "admin.users:write": AdminUsersWrite,
- "admin.auth.policy.assignEntities": AdminAuthPolicyAssignentities,
- "admin.auth.policy.removeEntities": AdminAuthPolicyRemoveentities,
- "admin.users.assign": AdminUsersAssign,
- "admin.users.invite": AdminUsersInvite,
- "admin.users.remove": AdminUsersRemove,
- "admin.users.session.clearSettings": AdminUsersSessionClearsettings,
- "admin.users.session.invalidate": AdminUsersSessionInvalidate,
- "admin.users.session.reset": AdminUsersSessionReset,
- "admin.users.session.resetBulk": AdminUsersSessionResetbulk,
- "admin.users.session.setSettings": AdminUsersSessionSetsettings,
- "admin.users.setAdmin": AdminUsersSetadmin,
- "admin.users.setExpiration": AdminUsersSetexpiration,
- "admin.users.setOwner": AdminUsersSetowner,
- "admin.users.setRegular": AdminUsersSetregular,
- "admin.users:read": AdminUsersRead,
- "admin.auth.policy.getEntities": AdminAuthPolicyGetentities,
- "admin.users.list": AdminUsersList,
- "admin.users.session.getSettings": AdminUsersSessionGetsettings,
- "admin.users.session.list": AdminUsersSessionList,
- "admin.users.unsupportedVersions.export": AdminUsersUnsupportedversionsExport,
- "admin.barriers:write": AdminBarriersWrite,
- "admin.barriers.create": AdminBarriersCreate,
- "admin.barriers.delete": AdminBarriersDelete,
- "admin.barriers.update": AdminBarriersUpdate,
- "admin.barriers:read": AdminBarriersRead,
- "admin.barriers.list": AdminBarriersList,
- "admin.conversations:write": AdminConversationsWrite,
- "admin.conversations.archive": AdminConversationsArchive,
- "admin.conversations.bulkArchive": AdminConversationsBulkarchive,
- "admin.conversations.bulkDelete": AdminConversationsBulkdelete,
- "admin.conversations.bulkMove": AdminConversationsBulkmove,
- "admin.conversations.convertToPrivate": AdminConversationsConverttoprivate,
- "admin.conversations.convertToPublic": AdminConversationsConverttopublic,
- "admin.conversations.create": AdminConversationsCreate,
- "admin.conversations.delete": AdminConversationsDelete,
- "admin.conversations.disconnectShared": AdminConversationsDisconnectshared,
- "admin.conversations.invite": AdminConversationsInvite,
- "admin.conversations.removeCustomRetention": AdminConversationsRemovecustomretention,
- "admin.conversations.rename": AdminConversationsRename,
- "admin.conversations.restrictAccess.addGroup": AdminConversationsRestrictaccessAddgroup,
- "admin.conversations.restrictAccess.removeGroup": AdminConversationsRestrictaccessRemovegroup,
- "admin.conversations.setConversationPrefs": AdminConversationsSetconversationprefs,
- "admin.conversations.setCustomRetention": AdminConversationsSetcustomretention,
- "admin.conversations.setTeams": AdminConversationsSetteams,
- "admin.conversations.unarchive": AdminConversationsUnarchive,
- "admin.conversations:read": AdminConversationsRead,
- "admin.conversations.ekm.listOriginalConnectedChannelInfo": AdminConversationsEkmListoriginalconnectedchannelinfo,
- "admin.conversations.getConversationPrefs": AdminConversationsGetconversationprefs,
- "admin.conversations.getCustomRetention": AdminConversationsGetcustomretention,
- "admin.conversations.getTeams": AdminConversationsGetteams,
- "admin.conversations.lookup": AdminConversationsLookup,
- "admin.conversations.restrictAccess.listGroups": AdminConversationsRestrictaccessListgroups,
- "admin.conversations.search": AdminConversationsSearch,
- "admin.teams:write": AdminTeamsWrite,
- "admin.emoji.add": AdminEmojiAdd,
- "admin.emoji.addAlias": AdminEmojiAddalias,
- "admin.emoji.remove": AdminEmojiRemove,
- "admin.teams.create": AdminTeamsCreate,
- "admin.teams.settings.setDefaultChannels": AdminTeamsSettingsSetdefaultchannels,
- "admin.teams.settings.setDescription": AdminTeamsSettingsSetdescription,
- "admin.teams.settings.setDiscoverability": AdminTeamsSettingsSetdiscoverability,
- "admin.teams.settings.setIcon": AdminTeamsSettingsSeticon,
- "admin.teams.settings.setName": AdminTeamsSettingsSetname,
- "admin.usergroups.addTeams": AdminUsergroupsAddteams,
- "admin.teams:read": AdminTeamsRead,
- "admin.emoji.list": AdminEmojiList,
- "admin.teams.admins.list": AdminTeamsAdminsList,
- "admin.teams.list": AdminTeamsList,
- "admin.teams.owners.list": AdminTeamsOwnersList,
- "admin.teams.settings.info": AdminTeamsSettingsInfo,
- "admin.workflows:read": AdminWorkflowsRead,
- "admin.functions.list": AdminFunctionsList,
- "admin.functions.permissions.lookup": AdminFunctionsPermissionsLookup,
- "admin.workflows.permissions.lookup": AdminWorkflowsPermissionsLookup,
- "admin.workflows.search": AdminWorkflowsSearch,
- "admin.workflows:write": AdminWorkflowsWrite,
- "admin.functions.permissions.set": AdminFunctionsPermissionsSet,
- "admin.workflows.collaborators.add": AdminWorkflowsCollaboratorsAdd,
- "admin.workflows.collaborators.remove": AdminWorkflowsCollaboratorsRemove,
- "admin.workflows.unpublish": AdminWorkflowsUnpublish,
- "admin.invites:write": AdminInvitesWrite,
- "admin.inviteRequests.approve": AdminInviterequestsApprove,
- "admin.inviteRequests.deny": AdminInviterequestsDeny,
- "admin.invites:read": AdminInvitesRead,
- "admin.inviteRequests.approved.list": AdminInviterequestsApprovedList,
- "admin.inviteRequests.denied.list": AdminInviterequestsDeniedList,
- "admin.inviteRequests.list": AdminInviterequestsList,
- "admin.roles:write": AdminRolesWrite,
- "admin.roles.addAssignments": AdminRolesAddassignments,
- "admin.roles.removeAssignments": AdminRolesRemoveassignments,
- "admin.roles:read": AdminRolesRead,
- "admin.roles.listAssignments": AdminRolesListassignments,
- "admin.usergroups:write": AdminUsergroupsWrite,
- "admin.usergroups.addChannels": AdminUsergroupsAddchannels,
- "admin.usergroups.removeChannels": AdminUsergroupsRemovechannels,
- "admin.usergroups:read": AdminUsergroupsRead,
- "admin.usergroups.listChannels": AdminUsergroupsListchannels,
- "hosting:read": HostingRead,
- "apps.activities.list": AppsActivitiesList,
- "connections:write": ConnectionsWrite,
- "apps.connections.open": AppsConnectionsOpen,
- "token": Token,
- "apps.datastore.bulkDelete": AppsDatastoreBulkdelete,
- "apps.datastore.bulkGet": AppsDatastoreBulkget,
- "apps.datastore.bulkPut": AppsDatastoreBulkput,
- "apps.datastore.delete": AppsDatastoreDelete,
- "apps.datastore.get": AppsDatastoreGet,
- "apps.datastore.put": AppsDatastorePut,
- "apps.datastore.query": AppsDatastoreQuery,
- "apps.datastore.update": AppsDatastoreUpdate,
- "datastore:read": DatastoreRead,
- "apps.datastore.count": AppsDatastoreCount,
- "authorizations:read": AuthorizationsRead,
- "apps.event.authorizations.list": AppsEventAuthorizationsList,
- "bot": Bot,
- "auth.revoke": AuthRevoke,
- "auth.test": AuthTest,
- "chat.getPermalink": ChatGetpermalink,
- "chat.scheduledMessages.list": ChatScheduledmessagesList,
- "dialog.open": DialogOpen,
- "functions.completeError": FunctionsCompleteerror,
- "functions.completeSuccess": FunctionsCompletesuccess,
- "rtm.connect": RtmConnect,
- "rtm.start": RtmStart,
- "views.open": ViewsOpen,
- "views.publish": ViewsPublish,
- "views.push": ViewsPush,
- "views.update": ViewsUpdate,
- "bookmarks:write": BookmarksWrite,
- "bookmarks.add": BookmarksAdd,
- "bookmarks.edit": BookmarksEdit,
- "bookmarks.remove": BookmarksRemove,
- "bookmarks:read": BookmarksRead,
- "bookmarks.list": BookmarksList,
- "users:read": UsersRead,
- "bots.info": BotsInfo,
- "users.getPresence": UsersGetpresence,
- "users.info": UsersInfo,
- "users.list": UsersList,
- "calls:write": CallsWrite,
- "calls.add": CallsAdd,
- "calls.end": CallsEnd,
- "calls.participants.add": CallsParticipantsAdd,
- "calls.participants.remove": CallsParticipantsRemove,
- "calls.update": CallsUpdate,
- "calls:read": CallsRead,
- "calls.info": CallsInfo,
- "channels:manage": ChannelsManage,
- "channels.create": ChannelsCreate,
- "channels.mark": ChannelsMark,
- "conversations.archive": ConversationsArchive,
- "conversations.close": ConversationsClose,
- "conversations.create": ConversationsCreate,
- "conversations.kick": ConversationsKick,
- "conversations.leave": ConversationsLeave,
- "conversations.mark": ConversationsMark,
- "conversations.open": ConversationsOpen,
- "conversations.rename": ConversationsRename,
- "conversations.unarchive": ConversationsUnarchive,
- "groups.create": GroupsCreate,
- "groups.mark": GroupsMark,
- "im.mark": ImMark,
- "im.open": ImOpen,
- "mpim.mark": MpimMark,
- "mpim.open": MpimOpen,
- "channels:read": ChannelsRead,
- "channels.info": ChannelsInfo,
- "conversations.info": ConversationsInfo,
- "conversations.list": ConversationsList,
- "conversations.members": ConversationsMembers,
- "groups.info": GroupsInfo,
- "im.list": ImList,
- "mpim.list": MpimList,
- "users.conversations": UsersConversations,
- "channels:write.invites": ChannelsWriteInvites,
- "channels.invite": ChannelsInvite,
- "conversations.invite": ConversationsInvite,
- "groups.invite": GroupsInvite,
- "chat:write": ChatWrite,
- "chat.delete": ChatDelete,
- "chat.deleteScheduledMessage": ChatDeletescheduledmessage,
- "chat.meMessage": ChatMemessage,
- "chat.postEphemeral": ChatPostephemeral,
- "chat.postMessage": ChatPostmessage,
- "chat.scheduleMessage": ChatSchedulemessage,
- "chat.update": ChatUpdate,
- "links:write": LinksWrite,
- "chat.unfurl": ChatUnfurl,
- "conversations.connect:write": ConversationsConnectWrite,
- "conversations.acceptSharedInvite": ConversationsAcceptsharedinvite,
- "conversations.inviteShared": ConversationsInviteshared,
- "conversations.connect:manage": ConversationsConnectManage,
- "conversations.approveSharedInvite": ConversationsApprovesharedinvite,
- "conversations.declineSharedInvite": ConversationsDeclinesharedinvite,
- "conversations.listConnectInvites": ConversationsListconnectinvites,
- "channels:history": ChannelsHistory,
- "conversations.history": ConversationsHistory,
- "conversations.replies": ConversationsReplies,
- "channels:join": ChannelsJoin,
- "conversations.join": ConversationsJoin,
- "channels:write.topic": ChannelsWriteTopic,
- "conversations.setPurpose": ConversationsSetpurpose,
- "conversations.setTopic": ConversationsSettopic,
- "dnd:write": DndWrite,
- "dnd.endDnd": DndEnddnd,
- "dnd.endSnooze": DndEndsnooze,
- "dnd.setSnooze": DndSetsnooze,
- "dnd:read": DndRead,
- "dnd.info": DndInfo,
- "dnd.teamInfo": DndTeaminfo,
- "emoji:read": EmojiRead,
- "emoji.list": EmojiList,
- "files:write": FilesWrite,
- "files.comments.delete": FilesCommentsDelete,
- "files.completeUploadExternal": FilesCompleteuploadexternal,
- "files.delete": FilesDelete,
- "files.getUploadURLExternal": FilesGetuploadurlexternal,
- "files.revokePublicURL": FilesRevokepublicurl,
- "files.sharedPublicURL": FilesSharedpublicurl,
- "files.upload": FilesUpload,
- "files:read": FilesRead,
- "files.info": FilesInfo,
- "files.list": FilesList,
- "remote_files:write": RemoteFilesWrite,
- "files.remote.add": FilesRemoteAdd,
- "files.remote.remove": FilesRemoteRemove,
- "files.remote.update": FilesRemoteUpdate,
- "remote_files:read": RemoteFilesRead,
- "files.remote.info": FilesRemoteInfo,
- "files.remote.list": FilesRemoteList,
- "remote_files:share": RemoteFilesShare,
- "files.remote.share": FilesRemoteShare,
- "app_configurations:write": AppConfigurationsWrite,
- "functions.distributions.permissions.add": FunctionsDistributionsPermissionsAdd,
- "functions.distributions.permissions.remove": FunctionsDistributionsPermissionsRemove,
- "functions.distributions.permissions.set": FunctionsDistributionsPermissionsSet,
- "app_configurations:read": AppConfigurationsRead,
- "functions.distributions.permissions.list": FunctionsDistributionsPermissionsList,
- "conversations": Conversations,
- "groups.open": GroupsOpen,
- "tokens.basic": TokensBasic,
- "migration.exchange": MigrationExchange,
- "email": Email,
- "openid.connect.userInfo": OpenidConnectUserinfo,
- "pins:write": PinsWrite,
- "pins.add": PinsAdd,
- "pins.remove": PinsRemove,
- "pins:read": PinsRead,
- "pins.list": PinsList,
- "reactions:write": ReactionsWrite,
- "reactions.add": ReactionsAdd,
- "reactions.remove": ReactionsRemove,
- "reactions:read": ReactionsRead,
- "reactions.get": ReactionsGet,
- "reactions.list": ReactionsList,
- "reminders:write": RemindersWrite,
- "reminders.add": RemindersAdd,
- "reminders.complete": RemindersComplete,
- "reminders.delete": RemindersDelete,
- "reminders:read": RemindersRead,
- "reminders.info": RemindersInfo,
- "reminders.list": RemindersList,
- "search:read": SearchRead,
- "search.all": SearchAll,
- "search.files": SearchFiles,
- "search.messages": SearchMessages,
- "stars:write": StarsWrite,
- "stars.add": StarsAdd,
- "stars.remove": StarsRemove,
- "stars:read": StarsRead,
- "stars.list": StarsList,
- "admin": Admin,
- "team.accessLogs": TeamAccesslogs,
- "team.billableInfo": TeamBillableinfo,
- "team.integrationLogs": TeamIntegrationlogs,
- "team.billing:read": TeamBillingRead,
- "team.billing.info": TeamBillingInfo,
- "team:read": TeamRead,
- "team.info": TeamInfo,
- "team.preferences:read": TeamPreferencesRead,
- "team.preferences.list": TeamPreferencesList,
- "users.profile:read": UsersProfileRead,
- "team.profile.get": TeamProfileGet,
- "users.profile.get": UsersProfileGet,
- "usergroups:write": UsergroupsWrite,
- "usergroups.create": UsergroupsCreate,
- "usergroups.disable": UsergroupsDisable,
- "usergroups.enable": UsergroupsEnable,
- "usergroups.update": UsergroupsUpdate,
- "usergroups.users.update": UsergroupsUsersUpdate,
- "usergroups:read": UsergroupsRead,
- "usergroups.list": UsergroupsList,
- "usergroups.users.list": UsergroupsUsersList,
- "users.profile:write": UsersProfileWrite,
- "users.deletePhoto": UsersDeletephoto,
- "users.profile.set": UsersProfileSet,
- "users.setPhoto": UsersSetphoto,
- "identity.basic": IdentityBasic,
- "users.identity": UsersIdentity,
- "users:read.email": UsersReadEmail,
- "users.lookupByEmail": UsersLookupbyemail,
- "users:write": UsersWrite,
- "users.setActive": UsersSetactive,
- "users.setPresence": UsersSetpresence,
- "workflow.steps:execute": WorkflowStepsExecute,
- "workflows.stepCompleted": WorkflowsStepcompleted,
- "workflows.stepFailed": WorkflowsStepfailed,
- "workflows.updateStep": WorkflowsUpdatestep,
- "triggers:write": TriggersWrite,
- "workflows.triggers.permissions.add": WorkflowsTriggersPermissionsAdd,
- "workflows.triggers.permissions.remove": WorkflowsTriggersPermissionsRemove,
- "workflows.triggers.permissions.set": WorkflowsTriggersPermissionsSet,
- "triggers:read": TriggersRead,
- "workflows.triggers.permissions.list": WorkflowsTriggersPermissionsList,
- }
-
- PermissionIDs = map[Permission]int{
- AdminAnalyticsRead: 1,
- AdminAnalyticsGetfile: 2,
- AdminAppActivitiesRead: 3,
- AdminAppsActivitiesList: 4,
- AdminAppsWrite: 5,
- AdminAppsApprove: 6,
- AdminAppsClearresolution: 7,
- AdminAppsConfigSet: 8,
- AdminAppsRequestsCancel: 9,
- AdminAppsRestrict: 10,
- AdminAppsUninstall: 11,
- AdminAppsRead: 12,
- AdminAppsApprovedList: 13,
- AdminAppsConfigLookup: 14,
- AdminAppsRequestsList: 15,
- AdminAppsRestrictedList: 16,
- AdminUsersWrite: 17,
- AdminAuthPolicyAssignentities: 18,
- AdminAuthPolicyRemoveentities: 19,
- AdminUsersAssign: 20,
- AdminUsersInvite: 21,
- AdminUsersRemove: 22,
- AdminUsersSessionClearsettings: 23,
- AdminUsersSessionInvalidate: 24,
- AdminUsersSessionReset: 25,
- AdminUsersSessionResetbulk: 26,
- AdminUsersSessionSetsettings: 27,
- AdminUsersSetadmin: 28,
- AdminUsersSetexpiration: 29,
- AdminUsersSetowner: 30,
- AdminUsersSetregular: 31,
- AdminUsersRead: 32,
- AdminAuthPolicyGetentities: 33,
- AdminUsersList: 34,
- AdminUsersSessionGetsettings: 35,
- AdminUsersSessionList: 36,
- AdminUsersUnsupportedversionsExport: 37,
- AdminBarriersWrite: 38,
- AdminBarriersCreate: 39,
- AdminBarriersDelete: 40,
- AdminBarriersUpdate: 41,
- AdminBarriersRead: 42,
- AdminBarriersList: 43,
- AdminConversationsWrite: 44,
- AdminConversationsArchive: 45,
- AdminConversationsBulkarchive: 46,
- AdminConversationsBulkdelete: 47,
- AdminConversationsBulkmove: 48,
- AdminConversationsConverttoprivate: 49,
- AdminConversationsConverttopublic: 50,
- AdminConversationsCreate: 51,
- AdminConversationsDelete: 52,
- AdminConversationsDisconnectshared: 53,
- AdminConversationsInvite: 54,
- AdminConversationsRemovecustomretention: 55,
- AdminConversationsRename: 56,
- AdminConversationsRestrictaccessAddgroup: 57,
- AdminConversationsRestrictaccessRemovegroup: 58,
- AdminConversationsSetconversationprefs: 59,
- AdminConversationsSetcustomretention: 60,
- AdminConversationsSetteams: 61,
- AdminConversationsUnarchive: 62,
- AdminConversationsRead: 63,
- AdminConversationsEkmListoriginalconnectedchannelinfo: 64,
- AdminConversationsGetconversationprefs: 65,
- AdminConversationsGetcustomretention: 66,
- AdminConversationsGetteams: 67,
- AdminConversationsLookup: 68,
- AdminConversationsRestrictaccessListgroups: 69,
- AdminConversationsSearch: 70,
- AdminTeamsWrite: 71,
- AdminEmojiAdd: 72,
- AdminEmojiAddalias: 73,
- AdminEmojiRemove: 74,
- AdminTeamsCreate: 75,
- AdminTeamsSettingsSetdefaultchannels: 76,
- AdminTeamsSettingsSetdescription: 77,
- AdminTeamsSettingsSetdiscoverability: 78,
- AdminTeamsSettingsSeticon: 79,
- AdminTeamsSettingsSetname: 80,
- AdminUsergroupsAddteams: 81,
- AdminTeamsRead: 82,
- AdminEmojiList: 83,
- AdminTeamsAdminsList: 84,
- AdminTeamsList: 85,
- AdminTeamsOwnersList: 86,
- AdminTeamsSettingsInfo: 87,
- AdminWorkflowsRead: 88,
- AdminFunctionsList: 89,
- AdminFunctionsPermissionsLookup: 90,
- AdminWorkflowsPermissionsLookup: 91,
- AdminWorkflowsSearch: 92,
- AdminWorkflowsWrite: 93,
- AdminFunctionsPermissionsSet: 94,
- AdminWorkflowsCollaboratorsAdd: 95,
- AdminWorkflowsCollaboratorsRemove: 96,
- AdminWorkflowsUnpublish: 97,
- AdminInvitesWrite: 98,
- AdminInviterequestsApprove: 99,
- AdminInviterequestsDeny: 100,
- AdminInvitesRead: 101,
- AdminInviterequestsApprovedList: 102,
- AdminInviterequestsDeniedList: 103,
- AdminInviterequestsList: 104,
- AdminRolesWrite: 105,
- AdminRolesAddassignments: 106,
- AdminRolesRemoveassignments: 107,
- AdminRolesRead: 108,
- AdminRolesListassignments: 109,
- AdminUsergroupsWrite: 110,
- AdminUsergroupsAddchannels: 111,
- AdminUsergroupsRemovechannels: 112,
- AdminUsergroupsRead: 113,
- AdminUsergroupsListchannels: 114,
- HostingRead: 115,
- AppsActivitiesList: 116,
- ConnectionsWrite: 117,
- AppsConnectionsOpen: 118,
- Token: 119,
- AppsDatastoreBulkdelete: 120,
- AppsDatastoreBulkget: 121,
- AppsDatastoreBulkput: 122,
- AppsDatastoreDelete: 123,
- AppsDatastoreGet: 124,
- AppsDatastorePut: 125,
- AppsDatastoreQuery: 126,
- AppsDatastoreUpdate: 127,
- DatastoreRead: 128,
- AppsDatastoreCount: 129,
- AuthorizationsRead: 130,
- AppsEventAuthorizationsList: 131,
- Bot: 132,
- AuthRevoke: 133,
- AuthTest: 134,
- ChatGetpermalink: 135,
- ChatScheduledmessagesList: 136,
- DialogOpen: 137,
- FunctionsCompleteerror: 138,
- FunctionsCompletesuccess: 139,
- RtmConnect: 140,
- RtmStart: 141,
- ViewsOpen: 142,
- ViewsPublish: 143,
- ViewsPush: 144,
- ViewsUpdate: 145,
- BookmarksWrite: 146,
- BookmarksAdd: 147,
- BookmarksEdit: 148,
- BookmarksRemove: 149,
- BookmarksRead: 150,
- BookmarksList: 151,
- UsersRead: 152,
- BotsInfo: 153,
- UsersGetpresence: 154,
- UsersInfo: 155,
- UsersList: 156,
- CallsWrite: 157,
- CallsAdd: 158,
- CallsEnd: 159,
- CallsParticipantsAdd: 160,
- CallsParticipantsRemove: 161,
- CallsUpdate: 162,
- CallsRead: 163,
- CallsInfo: 164,
- ChannelsManage: 165,
- ChannelsCreate: 166,
- ChannelsMark: 167,
- ConversationsArchive: 168,
- ConversationsClose: 169,
- ConversationsCreate: 170,
- ConversationsKick: 171,
- ConversationsLeave: 172,
- ConversationsMark: 173,
- ConversationsOpen: 174,
- ConversationsRename: 175,
- ConversationsUnarchive: 176,
- GroupsCreate: 177,
- GroupsMark: 178,
- ImMark: 179,
- ImOpen: 180,
- MpimMark: 181,
- MpimOpen: 182,
- ChannelsRead: 183,
- ChannelsInfo: 184,
- ConversationsInfo: 185,
- ConversationsList: 186,
- ConversationsMembers: 187,
- GroupsInfo: 188,
- ImList: 189,
- MpimList: 190,
- UsersConversations: 191,
- ChannelsWriteInvites: 192,
- ChannelsInvite: 193,
- ConversationsInvite: 194,
- GroupsInvite: 195,
- ChatWrite: 196,
- ChatDelete: 197,
- ChatDeletescheduledmessage: 198,
- ChatMemessage: 199,
- ChatPostephemeral: 200,
- ChatPostmessage: 201,
- ChatSchedulemessage: 202,
- ChatUpdate: 203,
- LinksWrite: 204,
- ChatUnfurl: 205,
- ConversationsConnectWrite: 206,
- ConversationsAcceptsharedinvite: 207,
- ConversationsInviteshared: 208,
- ConversationsConnectManage: 209,
- ConversationsApprovesharedinvite: 210,
- ConversationsDeclinesharedinvite: 211,
- ConversationsListconnectinvites: 212,
- ChannelsHistory: 213,
- ConversationsHistory: 214,
- ConversationsReplies: 215,
- ChannelsJoin: 216,
- ConversationsJoin: 217,
- ChannelsWriteTopic: 218,
- ConversationsSetpurpose: 219,
- ConversationsSettopic: 220,
- DndWrite: 221,
- DndEnddnd: 222,
- DndEndsnooze: 223,
- DndSetsnooze: 224,
- DndRead: 225,
- DndInfo: 226,
- DndTeaminfo: 227,
- EmojiRead: 228,
- EmojiList: 229,
- FilesWrite: 230,
- FilesCommentsDelete: 231,
- FilesCompleteuploadexternal: 232,
- FilesDelete: 233,
- FilesGetuploadurlexternal: 234,
- FilesRevokepublicurl: 235,
- FilesSharedpublicurl: 236,
- FilesUpload: 237,
- FilesRead: 238,
- FilesInfo: 239,
- FilesList: 240,
- RemoteFilesWrite: 241,
- FilesRemoteAdd: 242,
- FilesRemoteRemove: 243,
- FilesRemoteUpdate: 244,
- RemoteFilesRead: 245,
- FilesRemoteInfo: 246,
- FilesRemoteList: 247,
- RemoteFilesShare: 248,
- FilesRemoteShare: 249,
- AppConfigurationsWrite: 250,
- FunctionsDistributionsPermissionsAdd: 251,
- FunctionsDistributionsPermissionsRemove: 252,
- FunctionsDistributionsPermissionsSet: 253,
- AppConfigurationsRead: 254,
- FunctionsDistributionsPermissionsList: 255,
- Conversations: 256,
- GroupsOpen: 257,
- TokensBasic: 258,
- MigrationExchange: 259,
- Email: 260,
- OpenidConnectUserinfo: 261,
- PinsWrite: 262,
- PinsAdd: 263,
- PinsRemove: 264,
- PinsRead: 265,
- PinsList: 266,
- ReactionsWrite: 267,
- ReactionsAdd: 268,
- ReactionsRemove: 269,
- ReactionsRead: 270,
- ReactionsGet: 271,
- ReactionsList: 272,
- RemindersWrite: 273,
- RemindersAdd: 274,
- RemindersComplete: 275,
- RemindersDelete: 276,
- RemindersRead: 277,
- RemindersInfo: 278,
- RemindersList: 279,
- SearchRead: 280,
- SearchAll: 281,
- SearchFiles: 282,
- SearchMessages: 283,
- StarsWrite: 284,
- StarsAdd: 285,
- StarsRemove: 286,
- StarsRead: 287,
- StarsList: 288,
- Admin: 289,
- TeamAccesslogs: 290,
- TeamBillableinfo: 291,
- TeamIntegrationlogs: 292,
- TeamBillingRead: 293,
- TeamBillingInfo: 294,
- TeamRead: 295,
- TeamInfo: 296,
- TeamPreferencesRead: 297,
- TeamPreferencesList: 298,
- UsersProfileRead: 299,
- TeamProfileGet: 300,
- UsersProfileGet: 301,
- UsergroupsWrite: 302,
- UsergroupsCreate: 303,
- UsergroupsDisable: 304,
- UsergroupsEnable: 305,
- UsergroupsUpdate: 306,
- UsergroupsUsersUpdate: 307,
- UsergroupsRead: 308,
- UsergroupsList: 309,
- UsergroupsUsersList: 310,
- UsersProfileWrite: 311,
- UsersDeletephoto: 312,
- UsersProfileSet: 313,
- UsersSetphoto: 314,
- IdentityBasic: 315,
- UsersIdentity: 316,
- UsersReadEmail: 317,
- UsersLookupbyemail: 318,
- UsersWrite: 319,
- UsersSetactive: 320,
- UsersSetpresence: 321,
- WorkflowStepsExecute: 322,
- WorkflowsStepcompleted: 323,
- WorkflowsStepfailed: 324,
- WorkflowsUpdatestep: 325,
- TriggersWrite: 326,
- WorkflowsTriggersPermissionsAdd: 327,
- WorkflowsTriggersPermissionsRemove: 328,
- WorkflowsTriggersPermissionsSet: 329,
- TriggersRead: 330,
- WorkflowsTriggersPermissionsList: 331,
- }
-
- IdToPermission = map[int]Permission{
- 1: AdminAnalyticsRead,
- 2: AdminAnalyticsGetfile,
- 3: AdminAppActivitiesRead,
- 4: AdminAppsActivitiesList,
- 5: AdminAppsWrite,
- 6: AdminAppsApprove,
- 7: AdminAppsClearresolution,
- 8: AdminAppsConfigSet,
- 9: AdminAppsRequestsCancel,
- 10: AdminAppsRestrict,
- 11: AdminAppsUninstall,
- 12: AdminAppsRead,
- 13: AdminAppsApprovedList,
- 14: AdminAppsConfigLookup,
- 15: AdminAppsRequestsList,
- 16: AdminAppsRestrictedList,
- 17: AdminUsersWrite,
- 18: AdminAuthPolicyAssignentities,
- 19: AdminAuthPolicyRemoveentities,
- 20: AdminUsersAssign,
- 21: AdminUsersInvite,
- 22: AdminUsersRemove,
- 23: AdminUsersSessionClearsettings,
- 24: AdminUsersSessionInvalidate,
- 25: AdminUsersSessionReset,
- 26: AdminUsersSessionResetbulk,
- 27: AdminUsersSessionSetsettings,
- 28: AdminUsersSetadmin,
- 29: AdminUsersSetexpiration,
- 30: AdminUsersSetowner,
- 31: AdminUsersSetregular,
- 32: AdminUsersRead,
- 33: AdminAuthPolicyGetentities,
- 34: AdminUsersList,
- 35: AdminUsersSessionGetsettings,
- 36: AdminUsersSessionList,
- 37: AdminUsersUnsupportedversionsExport,
- 38: AdminBarriersWrite,
- 39: AdminBarriersCreate,
- 40: AdminBarriersDelete,
- 41: AdminBarriersUpdate,
- 42: AdminBarriersRead,
- 43: AdminBarriersList,
- 44: AdminConversationsWrite,
- 45: AdminConversationsArchive,
- 46: AdminConversationsBulkarchive,
- 47: AdminConversationsBulkdelete,
- 48: AdminConversationsBulkmove,
- 49: AdminConversationsConverttoprivate,
- 50: AdminConversationsConverttopublic,
- 51: AdminConversationsCreate,
- 52: AdminConversationsDelete,
- 53: AdminConversationsDisconnectshared,
- 54: AdminConversationsInvite,
- 55: AdminConversationsRemovecustomretention,
- 56: AdminConversationsRename,
- 57: AdminConversationsRestrictaccessAddgroup,
- 58: AdminConversationsRestrictaccessRemovegroup,
- 59: AdminConversationsSetconversationprefs,
- 60: AdminConversationsSetcustomretention,
- 61: AdminConversationsSetteams,
- 62: AdminConversationsUnarchive,
- 63: AdminConversationsRead,
- 64: AdminConversationsEkmListoriginalconnectedchannelinfo,
- 65: AdminConversationsGetconversationprefs,
- 66: AdminConversationsGetcustomretention,
- 67: AdminConversationsGetteams,
- 68: AdminConversationsLookup,
- 69: AdminConversationsRestrictaccessListgroups,
- 70: AdminConversationsSearch,
- 71: AdminTeamsWrite,
- 72: AdminEmojiAdd,
- 73: AdminEmojiAddalias,
- 74: AdminEmojiRemove,
- 75: AdminTeamsCreate,
- 76: AdminTeamsSettingsSetdefaultchannels,
- 77: AdminTeamsSettingsSetdescription,
- 78: AdminTeamsSettingsSetdiscoverability,
- 79: AdminTeamsSettingsSeticon,
- 80: AdminTeamsSettingsSetname,
- 81: AdminUsergroupsAddteams,
- 82: AdminTeamsRead,
- 83: AdminEmojiList,
- 84: AdminTeamsAdminsList,
- 85: AdminTeamsList,
- 86: AdminTeamsOwnersList,
- 87: AdminTeamsSettingsInfo,
- 88: AdminWorkflowsRead,
- 89: AdminFunctionsList,
- 90: AdminFunctionsPermissionsLookup,
- 91: AdminWorkflowsPermissionsLookup,
- 92: AdminWorkflowsSearch,
- 93: AdminWorkflowsWrite,
- 94: AdminFunctionsPermissionsSet,
- 95: AdminWorkflowsCollaboratorsAdd,
- 96: AdminWorkflowsCollaboratorsRemove,
- 97: AdminWorkflowsUnpublish,
- 98: AdminInvitesWrite,
- 99: AdminInviterequestsApprove,
- 100: AdminInviterequestsDeny,
- 101: AdminInvitesRead,
- 102: AdminInviterequestsApprovedList,
- 103: AdminInviterequestsDeniedList,
- 104: AdminInviterequestsList,
- 105: AdminRolesWrite,
- 106: AdminRolesAddassignments,
- 107: AdminRolesRemoveassignments,
- 108: AdminRolesRead,
- 109: AdminRolesListassignments,
- 110: AdminUsergroupsWrite,
- 111: AdminUsergroupsAddchannels,
- 112: AdminUsergroupsRemovechannels,
- 113: AdminUsergroupsRead,
- 114: AdminUsergroupsListchannels,
- 115: HostingRead,
- 116: AppsActivitiesList,
- 117: ConnectionsWrite,
- 118: AppsConnectionsOpen,
- 119: Token,
- 120: AppsDatastoreBulkdelete,
- 121: AppsDatastoreBulkget,
- 122: AppsDatastoreBulkput,
- 123: AppsDatastoreDelete,
- 124: AppsDatastoreGet,
- 125: AppsDatastorePut,
- 126: AppsDatastoreQuery,
- 127: AppsDatastoreUpdate,
- 128: DatastoreRead,
- 129: AppsDatastoreCount,
- 130: AuthorizationsRead,
- 131: AppsEventAuthorizationsList,
- 132: Bot,
- 133: AuthRevoke,
- 134: AuthTest,
- 135: ChatGetpermalink,
- 136: ChatScheduledmessagesList,
- 137: DialogOpen,
- 138: FunctionsCompleteerror,
- 139: FunctionsCompletesuccess,
- 140: RtmConnect,
- 141: RtmStart,
- 142: ViewsOpen,
- 143: ViewsPublish,
- 144: ViewsPush,
- 145: ViewsUpdate,
- 146: BookmarksWrite,
- 147: BookmarksAdd,
- 148: BookmarksEdit,
- 149: BookmarksRemove,
- 150: BookmarksRead,
- 151: BookmarksList,
- 152: UsersRead,
- 153: BotsInfo,
- 154: UsersGetpresence,
- 155: UsersInfo,
- 156: UsersList,
- 157: CallsWrite,
- 158: CallsAdd,
- 159: CallsEnd,
- 160: CallsParticipantsAdd,
- 161: CallsParticipantsRemove,
- 162: CallsUpdate,
- 163: CallsRead,
- 164: CallsInfo,
- 165: ChannelsManage,
- 166: ChannelsCreate,
- 167: ChannelsMark,
- 168: ConversationsArchive,
- 169: ConversationsClose,
- 170: ConversationsCreate,
- 171: ConversationsKick,
- 172: ConversationsLeave,
- 173: ConversationsMark,
- 174: ConversationsOpen,
- 175: ConversationsRename,
- 176: ConversationsUnarchive,
- 177: GroupsCreate,
- 178: GroupsMark,
- 179: ImMark,
- 180: ImOpen,
- 181: MpimMark,
- 182: MpimOpen,
- 183: ChannelsRead,
- 184: ChannelsInfo,
- 185: ConversationsInfo,
- 186: ConversationsList,
- 187: ConversationsMembers,
- 188: GroupsInfo,
- 189: ImList,
- 190: MpimList,
- 191: UsersConversations,
- 192: ChannelsWriteInvites,
- 193: ChannelsInvite,
- 194: ConversationsInvite,
- 195: GroupsInvite,
- 196: ChatWrite,
- 197: ChatDelete,
- 198: ChatDeletescheduledmessage,
- 199: ChatMemessage,
- 200: ChatPostephemeral,
- 201: ChatPostmessage,
- 202: ChatSchedulemessage,
- 203: ChatUpdate,
- 204: LinksWrite,
- 205: ChatUnfurl,
- 206: ConversationsConnectWrite,
- 207: ConversationsAcceptsharedinvite,
- 208: ConversationsInviteshared,
- 209: ConversationsConnectManage,
- 210: ConversationsApprovesharedinvite,
- 211: ConversationsDeclinesharedinvite,
- 212: ConversationsListconnectinvites,
- 213: ChannelsHistory,
- 214: ConversationsHistory,
- 215: ConversationsReplies,
- 216: ChannelsJoin,
- 217: ConversationsJoin,
- 218: ChannelsWriteTopic,
- 219: ConversationsSetpurpose,
- 220: ConversationsSettopic,
- 221: DndWrite,
- 222: DndEnddnd,
- 223: DndEndsnooze,
- 224: DndSetsnooze,
- 225: DndRead,
- 226: DndInfo,
- 227: DndTeaminfo,
- 228: EmojiRead,
- 229: EmojiList,
- 230: FilesWrite,
- 231: FilesCommentsDelete,
- 232: FilesCompleteuploadexternal,
- 233: FilesDelete,
- 234: FilesGetuploadurlexternal,
- 235: FilesRevokepublicurl,
- 236: FilesSharedpublicurl,
- 237: FilesUpload,
- 238: FilesRead,
- 239: FilesInfo,
- 240: FilesList,
- 241: RemoteFilesWrite,
- 242: FilesRemoteAdd,
- 243: FilesRemoteRemove,
- 244: FilesRemoteUpdate,
- 245: RemoteFilesRead,
- 246: FilesRemoteInfo,
- 247: FilesRemoteList,
- 248: RemoteFilesShare,
- 249: FilesRemoteShare,
- 250: AppConfigurationsWrite,
- 251: FunctionsDistributionsPermissionsAdd,
- 252: FunctionsDistributionsPermissionsRemove,
- 253: FunctionsDistributionsPermissionsSet,
- 254: AppConfigurationsRead,
- 255: FunctionsDistributionsPermissionsList,
- 256: Conversations,
- 257: GroupsOpen,
- 258: TokensBasic,
- 259: MigrationExchange,
- 260: Email,
- 261: OpenidConnectUserinfo,
- 262: PinsWrite,
- 263: PinsAdd,
- 264: PinsRemove,
- 265: PinsRead,
- 266: PinsList,
- 267: ReactionsWrite,
- 268: ReactionsAdd,
- 269: ReactionsRemove,
- 270: ReactionsRead,
- 271: ReactionsGet,
- 272: ReactionsList,
- 273: RemindersWrite,
- 274: RemindersAdd,
- 275: RemindersComplete,
- 276: RemindersDelete,
- 277: RemindersRead,
- 278: RemindersInfo,
- 279: RemindersList,
- 280: SearchRead,
- 281: SearchAll,
- 282: SearchFiles,
- 283: SearchMessages,
- 284: StarsWrite,
- 285: StarsAdd,
- 286: StarsRemove,
- 287: StarsRead,
- 288: StarsList,
- 289: Admin,
- 290: TeamAccesslogs,
- 291: TeamBillableinfo,
- 292: TeamIntegrationlogs,
- 293: TeamBillingRead,
- 294: TeamBillingInfo,
- 295: TeamRead,
- 296: TeamInfo,
- 297: TeamPreferencesRead,
- 298: TeamPreferencesList,
- 299: UsersProfileRead,
- 300: TeamProfileGet,
- 301: UsersProfileGet,
- 302: UsergroupsWrite,
- 303: UsergroupsCreate,
- 304: UsergroupsDisable,
- 305: UsergroupsEnable,
- 306: UsergroupsUpdate,
- 307: UsergroupsUsersUpdate,
- 308: UsergroupsRead,
- 309: UsergroupsList,
- 310: UsergroupsUsersList,
- 311: UsersProfileWrite,
- 312: UsersDeletephoto,
- 313: UsersProfileSet,
- 314: UsersSetphoto,
- 315: IdentityBasic,
- 316: UsersIdentity,
- 317: UsersReadEmail,
- 318: UsersLookupbyemail,
- 319: UsersWrite,
- 320: UsersSetactive,
- 321: UsersSetpresence,
- 322: WorkflowStepsExecute,
- 323: WorkflowsStepcompleted,
- 324: WorkflowsStepfailed,
- 325: WorkflowsUpdatestep,
- 326: TriggersWrite,
- 327: WorkflowsTriggersPermissionsAdd,
- 328: WorkflowsTriggersPermissionsRemove,
- 329: WorkflowsTriggersPermissionsSet,
- 330: TriggersRead,
- 331: WorkflowsTriggersPermissionsList,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/slack/permissions.yaml b/pkg/analyzer/analyzers/slack/permissions.yaml
deleted file mode 100644
index 16f56cc6ffec..000000000000
--- a/pkg/analyzer/analyzers/slack/permissions.yaml
+++ /dev/null
@@ -1,263 +0,0 @@
-permissions:
- - admin.analytics.getFile
- - admin.apps.activities.list
- - admin.apps.approve
- - admin.apps.clearResolution
- - admin.apps.config.set
- - admin.apps.requests.cancel
- - admin.apps.restrict
- - admin.apps.uninstall
- - admin.apps.approved.list
- - admin.apps.config.lookup
- - admin.apps.requests.list
- - admin.apps.restricted.list
- - admin.auth.policy.assignEntities
- - admin.auth.policy.removeEntities
- - admin.users.assign
- - admin.users.invite
- - admin.users.remove
- - admin.users.session.clearSettings
- - admin.users.session.invalidate
- - admin.users.session.reset
- - admin.users.session.resetBulk
- - admin.users.session.setSettings
- - admin.users.setAdmin
- - admin.users.setExpiration
- - admin.users.setOwner
- - admin.users.setRegular
- - admin.auth.policy.getEntities
- - admin.users.list
- - admin.users.session.getSettings
- - admin.users.session.list
- - admin.users.unsupportedVersions.export
- - admin.barriers.create
- - admin.barriers.delete
- - admin.barriers.update
- - admin.barriers.list
- - admin.conversations.archive
- - admin.conversations.bulkArchive
- - admin.conversations.bulkDelete
- - admin.conversations.bulkMove
- - admin.conversations.convertToPrivate
- - admin.conversations.convertToPublic
- - admin.conversations.create
- - admin.conversations.delete
- - admin.conversations.disconnectShared
- - admin.conversations.invite
- - admin.conversations.removeCustomRetention
- - admin.conversations.rename
- - admin.conversations.restrictAccess.addGroup
- - admin.conversations.restrictAccess.removeGroup
- - admin.conversations.setConversationPrefs
- - admin.conversations.setCustomRetention
- - admin.conversations.setTeams
- - admin.conversations.unarchive
- - admin.conversations.ekm.listOriginalConnectedChannelInfo
- - admin.conversations.getConversationPrefs
- - admin.conversations.getCustomRetention
- - admin.conversations.getTeams
- - admin.conversations.lookup
- - admin.conversations.restrictAccess.listGroups
- - admin.conversations.search
- - admin.emoji.add
- - admin.emoji.addAlias
- - admin.emoji.remove
- - admin.teams.create
- - admin.teams.settings.setDefaultChannels
- - admin.teams.settings.setDescription
- - admin.teams.settings.setDiscoverability
- - admin.teams.settings.setIcon
- - admin.teams.settings.setName
- - admin.usergroups.addTeams
- - admin.emoji.list
- - admin.teams.admins.list
- - admin.teams.list
- - admin.teams.owners.list
- - admin.teams.settings.info
- - admin.functions.list
- - admin.functions.permissions.lookup
- - admin.workflows.permissions.lookup
- - admin.workflows.search
- - admin.functions.permissions.set
- - admin.workflows.collaborators.add
- - admin.workflows.collaborators.remove
- - admin.workflows.unpublish
- - admin.inviteRequests.approve
- - admin.inviteRequests.deny
- - admin.inviteRequests.approved.list
- - admin.inviteRequests.denied.list
- - admin.inviteRequests.list
- - admin.roles.addAssignments
- - admin.roles.removeAssignments
- - admin.roles.listAssignments
- - admin.usergroups.addChannels
- - admin.usergroups.removeChannels
- - admin.usergroups.listChannels
- - apps.activities.list
- - apps.connections.open
- - token
- - apps.datastore.bulkDelete
- - apps.datastore.bulkGet
- - apps.datastore.bulkPut
- - apps.datastore.delete
- - apps.datastore.get
- - apps.datastore.put
- - apps.datastore.query
- - apps.datastore.update
- - apps.datastore.count
- - apps.event.authorizations.list
- - bot
- - auth.revoke
- - auth.test
- - chat.getPermalink
- - chat.scheduledMessages.list
- - dialog.open
- - functions.completeError
- - functions.completeSuccess
- - rtm.connect
- - rtm.start
- - views.open
- - views.publish
- - views.push
- - views.update
- - bookmarks.add
- - bookmarks.edit
- - bookmarks.remove
- - bookmarks.list
- - bots.info
- - users.getPresence
- - users.info
- - users.list
- - calls.add
- - calls.end
- - calls.participants.add
- - calls.participants.remove
- - calls.update
- - calls.info
- - channels.create
- - channels.mark
- - conversations.archive
- - conversations.close
- - conversations.create
- - conversations.kick
- - conversations.leave
- - conversations.mark
- - conversations.open
- - conversations.rename
- - conversations.unarchive
- - groups.create
- - groups.mark
- - im.mark
- - im.open
- - mpim.mark
- - mpim.open
- - channels.info
- - conversations.info
- - conversations.list
- - conversations.members
- - groups.info
- - im.list
- - mpim.list
- - users.conversations
- - channels.invite
- - conversations.invite
- - groups.invite
- - chat.delete
- - chat.deleteScheduledMessage
- - chat.meMessage
- - chat.postEphemeral
- - chat.postMessage
- - chat.scheduleMessage
- - chat.update
- - chat.unfurl
- - conversations.acceptSharedInvite
- - conversations.inviteShared
- - conversations.approveSharedInvite
- - conversations.declineSharedInvite
- - conversations.listConnectInvites
- - conversations.history
- - conversations.replies
- - conversations.join
- - conversations.setPurpose
- - conversations.setTopic
- - dnd.endDnd
- - dnd.endSnooze
- - dnd.setSnooze
- - dnd.info
- - dnd.teamInfo
- - emoji.list
- - files.comments.delete
- - files.completeUploadExternal
- - files.delete
- - files.getUploadURLExternal
- - files.revokePublicURL
- - files.sharedPublicURL
- - files.upload
- - files.info
- - files.list
- - files.remote.add
- - files.remote.remove
- - files.remote.update
- - files.remote.info
- - files.remote.list
- - files.remote.share
- - functions.distributions.permissions.add
- - functions.distributions.permissions.remove
- - functions.distributions.permissions.set
- - functions.distributions.permissions.list
- - conversations
- - groups.open
- - tokens.basic
- - migration.exchange
- - email
- - openid.connect.userInfo
- - pins.add
- - pins.remove
- - pins.list
- - reactions.add
- - reactions.remove
- - reactions.get
- - reactions.list
- - reminders.add
- - reminders.complete
- - reminders.delete
- - reminders.info
- - reminders.list
- - search.all
- - search.files
- - search.messages
- - stars.add
- - stars.remove
- - stars.list
- - admin
- - team.accessLogs
- - team.billableInfo
- - team.integrationLogs
- - team.billing.info
- - team.info
- - team.preferences.list
- - team.profile.get
- - users.profile.get
- - usergroups.create
- - usergroups.disable
- - usergroups.enable
- - usergroups.update
- - usergroups.users.update
- - usergroups.list
- - usergroups.users.list
- - users.deletePhoto
- - users.profile.set
- - users.setPhoto
- - identity.basic
- - users.identity
- - users.lookupByEmail
- - users.setActive
- - users.setPresence
- - workflows.stepCompleted
- - workflows.stepFailed
- - workflows.updateStep
- - workflows.triggers.permissions.add
- - workflows.triggers.permissions.remove
- - workflows.triggers.permissions.set
- - workflows.triggers.permissions.list
-
diff --git a/pkg/analyzer/analyzers/slack/scopes.go b/pkg/analyzer/analyzers/slack/scopes.go
deleted file mode 100644
index c5dba89a1d63..000000000000
--- a/pkg/analyzer/analyzers/slack/scopes.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package slack
-
-// SCOPES := []string{string} {
-
-// "admin.analytics:read" : {
-// "admin.analytics.getFile",
-// "admin.analytics.getUsage",
-// "admin.analytics.listFiles",
-// }
-// }
-
-var scope_mapping = map[string][]string{
- "admin.analytics:read": {"admin.analytics.getFile"},
- "admin.app_activities:read": {"admin.apps.activities.list"},
- "admin.apps:write": {"admin.apps.approve", "admin.apps.clearResolution", "admin.apps.config.set", "admin.apps.requests.cancel", "admin.apps.restrict", "admin.apps.uninstall"},
- "admin.apps:read": {"admin.apps.approved.list", "admin.apps.config.lookup", "admin.apps.requests.list", "admin.apps.restricted.list"},
- "admin.users:write": {"admin.auth.policy.assignEntities", "admin.auth.policy.removeEntities", "admin.users.assign", "admin.users.invite", "admin.users.remove", "admin.users.session.clearSettings", "admin.users.session.invalidate", "admin.users.session.reset", "admin.users.session.resetBulk", "admin.users.session.setSettings", "admin.users.setAdmin", "admin.users.setExpiration", "admin.users.setOwner", "admin.users.setRegular"},
- "admin.users:read": {"admin.auth.policy.getEntities", "admin.users.list", "admin.users.session.getSettings", "admin.users.session.list", "admin.users.unsupportedVersions.export"},
- "admin.barriers:write": {"admin.barriers.create", "admin.barriers.delete", "admin.barriers.update"},
- "admin.barriers:read": {"admin.barriers.list"},
- "admin.conversations:write": {"admin.conversations.archive", "admin.conversations.bulkArchive", "admin.conversations.bulkDelete", "admin.conversations.bulkMove", "admin.conversations.convertToPrivate", "admin.conversations.convertToPublic", "admin.conversations.create", "admin.conversations.delete", "admin.conversations.disconnectShared", "admin.conversations.invite", "admin.conversations.removeCustomRetention", "admin.conversations.rename", "admin.conversations.restrictAccess.addGroup", "admin.conversations.restrictAccess.removeGroup", "admin.conversations.setConversationPrefs", "admin.conversations.setCustomRetention", "admin.conversations.setTeams", "admin.conversations.unarchive"},
- "admin.conversations:read": {"admin.conversations.ekm.listOriginalConnectedChannelInfo", "admin.conversations.getConversationPrefs", "admin.conversations.getCustomRetention", "admin.conversations.getTeams", "admin.conversations.lookup", "admin.conversations.restrictAccess.listGroups", "admin.conversations.search"},
- "admin.teams:write": {"admin.emoji.add", "admin.emoji.addAlias", "admin.emoji.remove", "admin.emoji.rename", "admin.teams.create", "admin.teams.settings.setDefaultChannels", "admin.teams.settings.setDescription", "admin.teams.settings.setDiscoverability", "admin.teams.settings.setIcon", "admin.teams.settings.setName", "admin.usergroups.addTeams"},
- "admin.teams:read": {"admin.emoji.list", "admin.teams.admins.list", "admin.teams.list", "admin.teams.owners.list", "admin.teams.settings.info"},
- "admin.workflows:read": {"admin.functions.list", "admin.functions.permissions.lookup", "admin.workflows.permissions.lookup", "admin.workflows.search"},
- "admin.workflows:write": {"admin.functions.permissions.set", "admin.workflows.collaborators.add", "admin.workflows.collaborators.remove", "admin.workflows.unpublish"},
- "admin.invites:write": {"admin.inviteRequests.approve", "admin.inviteRequests.deny"},
- "admin.invites:read": {"admin.inviteRequests.approved.list", "admin.inviteRequests.denied.list", "admin.inviteRequests.list"},
- "admin.roles:write": {"admin.roles.addAssignments", "admin.roles.removeAssignments"},
- "admin.roles:read": {"admin.roles.listAssignments"},
- "admin.usergroups:write": {"admin.usergroups.addChannels", "admin.usergroups.removeChannels"},
- "admin.usergroups:read": {"admin.usergroups.listChannels"},
- "hosting:read": {"apps.activities.list"},
- "connections:write": {"apps.connections.open"},
- "token": {"apps.datastore.bulkDelete", "apps.datastore.bulkGet", "apps.datastore.bulkPut", "apps.datastore.delete", "apps.datastore.get", "apps.datastore.put", "apps.datastore.query", "apps.datastore.update"},
- "datastore:read": {"apps.datastore.count"},
- "authorizations:read": {"apps.event.authorizations.list"},
- "bot": {"auth.revoke", "auth.test", "chat.getPermalink", "chat.scheduledMessages.list", "dialog.open", "functions.completeError", "functions.completeSuccess", "rtm.connect", "rtm.start", "views.open", "views.publish", "views.push", "views.update"},
- "bookmarks:write": {"bookmarks.add", "bookmarks.edit", "bookmarks.remove"},
- "bookmarks:read": {"bookmarks.list"},
- "users:read": {"bots.info", "users.getPresence", "users.info", "users.list"},
- "calls:write": {"calls.add", "calls.end", "calls.participants.add", "calls.participants.remove", "calls.update"},
- "calls:read": {"calls.info"},
- "channels:manage": {"channels.create", "channels.mark", "conversations.archive", "conversations.close", "conversations.create", "conversations.kick", "conversations.leave", "conversations.mark", "conversations.open", "conversations.rename", "conversations.unarchive", "groups.create", "groups.mark", "im.mark", "im.open", "mpim.mark", "mpim.open"},
- "channels:read": {"channels.info", "conversations.info", "conversations.list", "conversations.members", "groups.info", "im.list", "mpim.list", "users.conversations"},
- "channels:write.invites": {"channels.invite", "conversations.invite", "groups.invite"},
- "chat:write": {"chat.delete", "chat.deleteScheduledMessage", "chat.meMessage", "chat.postEphemeral", "chat.postMessage", "chat.scheduleMessage", "chat.update"},
- "links:write": {"chat.unfurl"},
- "conversations.connect:write": {"conversations.acceptSharedInvite", "conversations.inviteShared"},
- "conversations.connect:manage": {"conversations.approveSharedInvite", "conversations.declineSharedInvite", "conversations.listConnectInvites"},
- "channels:history": {"conversations.history", "conversations.replies"},
- "channels:join": {"conversations.join"},
- "channels:write.topic": {"conversations.setPurpose", "conversations.setTopic"},
- "dnd:write": {"dnd.endDnd", "dnd.endSnooze", "dnd.setSnooze"},
- "dnd:read": {"dnd.info", "dnd.teamInfo"},
- "emoji:read": {"emoji.list"},
- "files:write": {"files.comments.delete", "files.completeUploadExternal", "files.delete", "files.getUploadURLExternal", "files.revokePublicURL", "files.sharedPublicURL", "files.upload"},
- "files:read": {"files.info", "files.list"},
- "remote_files:write": {"files.remote.add", "files.remote.remove", "files.remote.update"},
- "remote_files:read": {"files.remote.info", "files.remote.list"},
- "remote_files:share": {"files.remote.share"},
- "app_configurations:write": {"functions.distributions.permissions.add", "functions.distributions.permissions.remove", "functions.distributions.permissions.set"},
- "app_configurations:read": {"functions.distributions.permissions.list"},
- "conversations": {"groups.open"},
- "tokens.basic": {"migration.exchange"},
- "email": {"openid.connect.userInfo"},
- "pins:write": {"pins.add", "pins.remove"},
- "pins:read": {"pins.list"},
- "reactions:write": {"reactions.add", "reactions.remove"},
- "reactions:read": {"reactions.get", "reactions.list"},
- "reminders:write": {"reminders.add", "reminders.complete", "reminders.delete"},
- "reminders:read": {"reminders.info", "reminders.list"},
- "search:read": {"search.all", "search.files", "search.messages"},
- "stars:write": {"stars.add", "stars.remove"},
- "stars:read": {"stars.list"},
- "admin": {"team.accessLogs", "team.billableInfo", "team.integrationLogs"},
- "team.billing:read": {"team.billing.info"},
- "team:read": {"team.info"},
- "team.preferences:read": {"team.preferences.list"},
- "users.profile:read": {"team.profile.get", "users.profile.get"},
- "usergroups:write": {"usergroups.create", "usergroups.disable", "usergroups.enable", "usergroups.update", "usergroups.users.update"},
- "usergroups:read": {"usergroups.list", "usergroups.users.list"},
- "users.profile:write": {"users.deletePhoto", "users.profile.set", "users.setPhoto"},
- "identity.basic": {"users.identity"},
- "users:read.email": {"users.lookupByEmail"},
- "users:write": {"users.setActive", "users.setPresence"},
- "workflow.steps:execute": {"workflows.stepCompleted", "workflows.stepFailed", "workflows.updateStep"},
- "triggers:write": {"workflows.triggers.permissions.add", "workflows.triggers.permissions.remove", "workflows.triggers.permissions.set"},
- "triggers:read": {"workflows.triggers.permissions.list"},
-}
diff --git a/pkg/analyzer/analyzers/slack/slack.go b/pkg/analyzer/analyzers/slack/slack.go
deleted file mode 100644
index 273bc90c2a1a..000000000000
--- a/pkg/analyzer/analyzers/slack/slack.go
+++ /dev/null
@@ -1,242 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go slack
-
-package slack
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "os"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeSlack }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("key not found in credentialInfo")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeSlack,
- Metadata: nil,
- }
-
- resourceType := "user"
- fullyQualifiedName := info.User.TeamId + "/" + info.User.UserId
- if info.User.BotId != "" {
- resourceType = "bot"
- fullyQualifiedName = info.User.BotId
- }
- resource := analyzers.Resource{
- Name: info.User.User,
- FullyQualifiedName: fullyQualifiedName,
- Type: resourceType,
- Metadata: map[string]any{
- "url": info.User.Url,
- "team": info.User.Team,
- "team_id": info.User.TeamId,
- "scopes": strings.Split(info.Scopes, ","),
- },
- }
-
- // extract all permissions
- permissions := extractPermissions(info)
-
- result.Bindings = analyzers.BindAllPermissions(resource, permissions...)
-
- return &result
-}
-
-func extractPermissions(info *SecretInfo) []analyzers.Permission {
- var permissions []analyzers.Permission
-
- for _, scope := range strings.Split(info.Scopes, ",") {
- perms, ok := scope_mapping[scope]
- if !ok {
- continue
- }
-
- for _, perm := range perms {
- if _, ok := StringToPermission[perm]; !ok {
- // not in out generated permissions,
- continue
- }
-
- permissions = append(permissions, analyzers.Permission{
- Value: perm,
- Parent: nil,
- })
- }
- }
-
- return permissions
-}
-
-// Add in showAll to printScopes + deal with testing enterprise + add scope details
-
-type SlackUserData struct {
- Ok bool `json:"ok"`
- Url string `json:"url"`
- Team string `json:"team"`
- User string `json:"user"`
- TeamId string `json:"team_id"`
- UserId string `json:"user_id"`
- BotId string `json:"bot_id"`
- IsEnterprise bool `json:"is_enterprise"`
-}
-
-type SecretInfo struct {
- Scopes string
- User SlackUserData
-}
-
-func getSlackOAuthScopes(cfg *config.Config, key string) (scopes string, userData SlackUserData, err error) {
- userData = SlackUserData{}
- scopes = ""
-
- // URL to which the request will be sent
- url := "https://slack.com/api/auth.test"
-
- // Create a client to send the request
- client := analyzers.NewAnalyzeClient(cfg)
-
- // Create the request
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- return scopes, userData, err
- }
-
- // Add the Authorization header to the request
- req.Header.Add("Authorization", "Bearer "+key)
-
- // Send the request
- resp, err := client.Do(req)
- if err != nil {
- return scopes, userData, err
- }
- defer resp.Body.Close() // Close the response body when the function returns
-
- // print body
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return scopes, userData, err
- }
-
- // Unmarshal the response body into the SlackUserData struct
- if err := json.Unmarshal(body, &userData); err != nil {
- return scopes, userData, err
- }
-
- // Print all headers received from the server
- scopes = resp.Header.Get("X-Oauth-Scopes")
- return scopes, userData, err
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error: %v", err)
- return
- }
-
- color.Green("[!] Valid Slack API Key\n\n")
- printIdentityInfo(info.User)
- printScopes(strings.Split(info.Scopes, ","))
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- scopes, userData, err := getSlackOAuthScopes(cfg, key)
- if err != nil {
- return nil, fmt.Errorf("error getting Slack OAuth scopes: %w", err)
- }
-
- if !userData.Ok {
- return nil, fmt.Errorf("invalid Slack token")
- }
-
- return &SecretInfo{
- Scopes: scopes,
- User: userData,
- }, nil
-}
-
-func printIdentityInfo(userData SlackUserData) {
- if userData.Url != "" {
- color.Green("URL: %v", userData.Url)
- }
- if userData.Team != "" {
- color.Green("Team: %v", userData.Team)
- }
- if userData.User != "" {
- color.Green("User: %v", userData.User)
- }
- if userData.TeamId != "" {
- color.Green("Team ID: %v", userData.TeamId)
- }
- if userData.UserId != "" {
- color.Green("User ID: %v", userData.UserId)
- }
- if userData.BotId != "" {
- color.Green("Bot ID: %v", userData.BotId)
- }
- fmt.Println("")
- if userData.IsEnterprise {
- color.Green("[!] Slack is Enterprise")
- } else {
- color.Yellow("[-] Slack is not Enterprise")
- }
- fmt.Println("")
-}
-
-func printScopes(scopes []string) {
- t := table.NewWriter()
- // if !showAll {
- // t.SetOutputMirror(os.Stdout)
- // t.AppendHeader(table.Row{"Scopes"})
- // for _, scope := range scopes {
- // t.AppendRow([]interface{}{color.GreenString(scope)})
- // }
- // } else {
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Scope", "Permissions"})
- for _, scope := range scopes {
- perms := scope_mapping[scope]
- if perms == nil {
- t.AppendRow([]interface{}{color.GreenString(scope), color.GreenString("")})
- } else {
- t.AppendRow([]interface{}{color.GreenString(scope), color.GreenString(strings.Join(perms, ", "))})
- }
-
- }
- //}
-
- t.Render()
-
-}
diff --git a/pkg/analyzer/analyzers/slack/slack_test.go b/pkg/analyzer/analyzers/slack/slack_test.go
deleted file mode 100644
index bfd4bdd500f1..000000000000
--- a/pkg/analyzer/analyzers/slack/slack_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package slack
-
-import (
- _ "embed"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid Slack key",
- key: testSecrets.MustGetField("SLACK"),
- want: string(expectedOutput),
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/sourcegraph/permissions.go b/pkg/analyzer/analyzers/sourcegraph/permissions.go
deleted file mode 100644
index fe395efb20f8..000000000000
--- a/pkg/analyzer/analyzers/sourcegraph/permissions.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package sourcegraph
-
-import "errors"
-
-type Permission int
-
-const (
- NoAccess Permission = iota
- UserRead Permission = iota
- SiteAdminFull Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- UserRead: "user:read",
- SiteAdminFull: "site_admin:full",
- }
-
- StringToPermission = map[string]Permission{
- "user:read": UserRead,
- "site_admin:full": SiteAdminFull,
- }
-
- PermissionIDs = map[Permission]int{
- UserRead: 0,
- SiteAdminFull: 1,
- }
-
- IdToPermission = map[int]Permission{
- 0: UserRead,
- 1: SiteAdminFull,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/sourcegraph/permissions.yaml b/pkg/analyzer/analyzers/sourcegraph/permissions.yaml
deleted file mode 100644
index 67cf1d738f3b..000000000000
--- a/pkg/analyzer/analyzers/sourcegraph/permissions.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-permissions:
-- user:read
-- site_admin:full
diff --git a/pkg/analyzer/analyzers/sourcegraph/sourcegraph.go b/pkg/analyzer/analyzers/sourcegraph/sourcegraph.go
deleted file mode 100644
index f1fa2532646c..000000000000
--- a/pkg/analyzer/analyzers/sourcegraph/sourcegraph.go
+++ /dev/null
@@ -1,213 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go sourcegraph
-package sourcegraph
-
-// ToDo: Add support for custom domain
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "strings"
-
- "github.com/fatih/color"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeSourcegraph }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, fmt.Errorf("missing key in credInfo")
- }
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
-
- permission := PermissionStrings[UserRead]
- if info.IsSiteAdmin {
- permission = PermissionStrings[SiteAdminFull]
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeSourcegraph,
- Metadata: nil,
- Bindings: []analyzers.Binding{
- {
- Resource: analyzers.Resource{
- Name: info.User.Data.CurrentUser.Username,
- FullyQualifiedName: "sourcegraph/" + info.User.Data.CurrentUser.Email,
- Type: "user",
- Metadata: map[string]any{
- "created_at": info.User.Data.CurrentUser.CreatedAt,
- "email": info.User.Data.CurrentUser.Email,
- },
- Parent: nil,
- },
- Permission: analyzers.Permission{
- Value: permission,
- },
- },
- },
- }
-
- return &result
-}
-
-type GraphQLError struct {
- Message string `json:"message"`
- Path []string `json:"path"`
-}
-
-type GraphQLResponse struct {
- Errors []GraphQLError `json:"errors"`
- Data interface{} `json:"data"`
-}
-
-type UserInfoJSON struct {
- Data struct {
- CurrentUser struct {
- Username string `json:"username"`
- Email string `json:"email"`
- SiteAdmin bool `json:"siteAdmin"`
- CreatedAt string `json:"createdAt"`
- } `json:"currentUser"`
- } `json:"data"`
-}
-
-type SecretInfo struct {
- User UserInfoJSON
- IsSiteAdmin bool
-}
-
-func getUserInfo(cfg *config.Config, key string) (UserInfoJSON, error) {
- var userInfo UserInfoJSON
-
- // POST request is considered as non-safe and sourcegraph has graphql APIs. They do not change any state.
- // We are using unrestricted client to avoid error for non-safe API request.
- client := analyzers.NewAnalyzeClientUnrestricted(cfg)
- payload := "{\"query\":\"query { currentUser { username, email, siteAdmin, createdAt } }\"}"
- req, err := http.NewRequest("POST", "https://sourcegraph.com/.api/graphql", strings.NewReader(payload))
- if err != nil {
- return userInfo, err
- }
-
- req.Header.Set("Authorization", "token "+key)
-
- resp, err := client.Do(req)
- if err != nil {
- return userInfo, err
- }
-
- defer resp.Body.Close()
-
- err = json.NewDecoder(resp.Body).Decode(&userInfo)
- if err != nil {
- return userInfo, err
- }
- return userInfo, nil
-}
-
-func checkSiteAdmin(cfg *config.Config, key string) (bool, error) {
- query := `
- {
- "query": "query webhooks($first: Int, $after: String, $kind: ExternalServiceKind) { webhooks(first: $first, after: $after, kind: $kind) { totalCount } }",
- "variables": {
- "first": 10,
- "after": "",
- "kind": "GITHUB"
- }
- }`
-
- // POST request is considered as non-safe and sourcegraph has graphql APIs. They do not change any state.
- // We are using unrestricted client to avoid error for non-safe API request.
- client := analyzers.NewAnalyzeClientUnrestricted(cfg)
- req, err := http.NewRequest("POST", "https://sourcegraph.com/.api/graphql", strings.NewReader(query))
- if err != nil {
- return false, err
- }
-
- req.Header.Set("Authorization", "token "+key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer resp.Body.Close()
-
- var response GraphQLResponse
-
- err = json.NewDecoder(resp.Body).Decode(&response)
- if err != nil {
- return false, err
- }
-
- if len(response.Errors) > 0 {
- return false, nil
- }
- return true, nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- // ToDo: Add in logging
- if cfg.LoggingEnabled {
- color.Red("[x] Logging is not supported for this analyzer.")
- return
- }
-
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
-
- color.Green("[!] Valid Sourcegraph Access Token\n\n")
- color.Yellow("[i] Sourcegraph User Information\n")
- color.Green("Username: %s\n", info.User.Data.CurrentUser.Username)
- color.Green("Email: %s\n", info.User.Data.CurrentUser.Email)
- color.Green("Created At: %s\n\n", info.User.Data.CurrentUser.CreatedAt)
-
- if info.IsSiteAdmin {
- color.Green("[!] Token Permissions: Site Admin")
- } else {
- // This is the default for all access tokens as of 6/11/24
- color.Yellow("[i] Token Permissions: user:full (default)")
- }
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- userInfo, err := getUserInfo(cfg, key)
- if err != nil {
- return nil, err
- }
-
- if userInfo.Data.CurrentUser.Username == "" {
- return nil, fmt.Errorf("invalid Sourcegraph Access Token")
- }
-
- isSiteAdmin, err := checkSiteAdmin(cfg, key)
- if err != nil {
- return nil, err
- }
-
- return &SecretInfo{
- User: userInfo,
- IsSiteAdmin: isSiteAdmin,
- }, nil
-}
diff --git a/pkg/analyzer/analyzers/sourcegraph/sourcegraph_test.go b/pkg/analyzer/analyzers/sourcegraph/sourcegraph_test.go
deleted file mode 100644
index 31e948d24904..000000000000
--- a/pkg/analyzer/analyzers/sourcegraph/sourcegraph_test.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package sourcegraph
-
-import (
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- secret := testSecrets.MustGetField("SOURCEGRAPH")
-
- tests := []struct {
- name string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid SourceGraph key",
- key: secret,
- want: `{
- "AnalyzerType": 17,
- "Bindings": [
- {
- "Resource": {
- "Name": "ahrav",
- "FullyQualifiedName": "sourcegraph/ahravdutta02@gmail.com",
- "Type": "user",
- "Metadata": {
- "created_at": "2023-07-23T04:16:31Z",
- "email": "ahravdutta02@gmail.com"
- },
- "Parent": null
- },
- "Permission": {
- "Value": "user:read",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": null
- }`,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/analyzers/square/expected_output.json b/pkg/analyzer/analyzers/square/expected_output.json
deleted file mode 100644
index 9020e5a66e44..000000000000
--- a/pkg/analyzer/analyzers/square/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":18,"Bindings":[{"Resource":{"Name":"AcceptDispute","FullyQualifiedName":"AcceptDispute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Disputes","FullyQualifiedName":"Disputes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DISPUTES_WRITE","Parent":null}},{"Resource":{"Name":"AccumulateLoyaltyPoints","FullyQualifiedName":"AccumulateLoyaltyPoints","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_WRITE","Parent":null}},{"Resource":{"Name":"AddGroupToCustomer","FullyQualifiedName":"AddGroupToCustomer","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"AdjustLoyaltyPoints","FullyQualifiedName":"AdjustLoyaltyPoints","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_WRITE","Parent":null}},{"Resource":{"Name":"BatchChangeInventory","FullyQualifiedName":"BatchChangeInventory","Type":"endpoint","Metadata":null,"Parent":{"Name":"Inventory","FullyQualifiedName":"Inventory","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVENTORY_WRITE","Parent":null}},{"Resource":{"Name":"BatchDeleteCatalogObjects","FullyQualifiedName":"BatchDeleteCatalogObjects","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_WRITE","Parent":null}},{"Resource":{"Name":"BatchRetrieveCatalogObjects","FullyQualifiedName":"BatchRetrieveCatalogObjects","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_READ","Parent":null}},{"Resource":{"Name":"BatchRetrieveInventoryChanges","FullyQualifiedName":"BatchRetrieveInventoryChanges","Type":"endpoint","Metadata":null,"Parent":{"Name":"Inventory","FullyQualifiedName":"Inventory","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVENTORY_READ","Parent":null}},{"Resource":{"Name":"BatchRetrieveInventoryCounts","FullyQualifiedName":"BatchRetrieveInventoryCounts","Type":"endpoint","Metadata":null,"Parent":{"Name":"Inventory","FullyQualifiedName":"Inventory","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVENTORY_READ","Parent":null}},{"Resource":{"Name":"BatchRetrieveOrders","FullyQualifiedName":"BatchRetrieveOrders","Type":"endpoint","Metadata":null,"Parent":{"Name":"Orders","FullyQualifiedName":"Orders","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_READ","Parent":null}},{"Resource":{"Name":"BatchUpsertCatalogObjects","FullyQualifiedName":"BatchUpsertCatalogObjects","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_WRITE","Parent":null}},{"Resource":{"Name":"BulkCreateCustomers","FullyQualifiedName":"BulkCreateCustomers","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"BulkCreateTeamMembers","FullyQualifiedName":"BulkCreateTeamMembers","Type":"endpoint","Metadata":null,"Parent":{"Name":"Team","FullyQualifiedName":"Team","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_WRITE","Parent":null}},{"Resource":{"Name":"BulkCreateVendors","FullyQualifiedName":"BulkCreateVendors","Type":"endpoint","Metadata":null,"Parent":{"Name":"Vendors","FullyQualifiedName":"Vendors","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"VENDOR_WRITE","Parent":null}},{"Resource":{"Name":"BulkDeleteCustomers","FullyQualifiedName":"BulkDeleteCustomers","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"BulkDeleteLocationCustomAttributes","FullyQualifiedName":"BulkDeleteLocationCustomAttributes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Location Custom Attributes","FullyQualifiedName":"Location Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"BulkDeleteMerchantCustomAttributes","FullyQualifiedName":"BulkDeleteMerchantCustomAttributes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchant Custom Attributes","FullyQualifiedName":"Merchant Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"BulkDeleteOrderCustomAttributes","FullyQualifiedName":"BulkDeleteOrderCustomAttributes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Order Custom Attributes","FullyQualifiedName":"Order Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"BulkRetrieveCustomers","FullyQualifiedName":"BulkRetrieveCustomers","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"BulkRetrieveVendors","FullyQualifiedName":"BulkRetrieveVendors","Type":"endpoint","Metadata":null,"Parent":{"Name":"Vendors","FullyQualifiedName":"Vendors","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"VENDOR_READ","Parent":null}},{"Resource":{"Name":"BulkUpdateCustomers","FullyQualifiedName":"BulkUpdateCustomers","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"BulkUpdateTeamMembers","FullyQualifiedName":"BulkUpdateTeamMembers","Type":"endpoint","Metadata":null,"Parent":{"Name":"Team","FullyQualifiedName":"Team","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_WRITE","Parent":null}},{"Resource":{"Name":"BulkUpdateVendors","FullyQualifiedName":"BulkUpdateVendors","Type":"endpoint","Metadata":null,"Parent":{"Name":"Vendors","FullyQualifiedName":"Vendors","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"VENDOR_WRITE","Parent":null}},{"Resource":{"Name":"BulkUpsertBookingCustomAttributes (buyer-level)","FullyQualifiedName":"BulkUpsertBookingCustomAttributes (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"BulkUpsertBookingCustomAttributes (seller-level)","FullyQualifiedName":"BulkUpsertBookingCustomAttributes (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_WRITE","Parent":null}},{"Resource":{"Name":"BulkUpsertBookingCustomAttributes (seller-level)","FullyQualifiedName":"BulkUpsertBookingCustomAttributes (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"BulkUpsertCustomerCustomAttributes","FullyQualifiedName":"BulkUpsertCustomerCustomAttributes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Custom Attributes","FullyQualifiedName":"Customer Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"BulkUpsertLocationCustomAttributes","FullyQualifiedName":"BulkUpsertLocationCustomAttributes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Location Custom Attributes","FullyQualifiedName":"Location Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"BulkUpsertMerchantCustomAttributes","FullyQualifiedName":"BulkUpsertMerchantCustomAttributes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchant Custom Attributes","FullyQualifiedName":"Merchant Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"BulkUpsertOrderCustomAttributes","FullyQualifiedName":"BulkUpsertOrderCustomAttributes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Order Custom Attributes","FullyQualifiedName":"Order Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"CalculateLoyaltyPoints","FullyQualifiedName":"CalculateLoyaltyPoints","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_READ","Parent":null}},{"Resource":{"Name":"CancelBooking (buyer-level)","FullyQualifiedName":"CancelBooking (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CancelBooking (seller-level)","FullyQualifiedName":"CancelBooking (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_WRITE","Parent":null}},{"Resource":{"Name":"CancelBooking (seller-level)","FullyQualifiedName":"CancelBooking (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CancelInvoice","FullyQualifiedName":"CancelInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"CancelInvoice","FullyQualifiedName":"CancelInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"CancelLoyaltyPromotion","FullyQualifiedName":"CancelLoyaltyPromotion","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_WRITE","Parent":null}},{"Resource":{"Name":"CancelPayment","FullyQualifiedName":"CancelPayment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CancelPaymentByIdempotencyKey","FullyQualifiedName":"CancelPaymentByIdempotencyKey","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CancelSubscription","FullyQualifiedName":"CancelSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"SUBSCRIPTIONS_WRITE","Parent":null}},{"Resource":{"Name":"CancelTerminalAction","FullyQualifiedName":"CancelTerminalAction","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CancelTerminalCheckout","FullyQualifiedName":"CancelTerminalCheckout","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CancelTerminalRefund","FullyQualifiedName":"CancelTerminalRefund","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CatalogInfo","FullyQualifiedName":"CatalogInfo","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_READ","Parent":null}},{"Resource":{"Name":"CloneOrder","FullyQualifiedName":"CloneOrder","Type":"endpoint","Metadata":null,"Parent":{"Name":"Orders","FullyQualifiedName":"Orders","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"CompletePayment","FullyQualifiedName":"CompletePayment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreateBooking (buyer-level)","FullyQualifiedName":"CreateBooking (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreateBooking (seller-level)","FullyQualifiedName":"CreateBooking (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_WRITE","Parent":null}},{"Resource":{"Name":"CreateBooking (seller-level)","FullyQualifiedName":"CreateBooking (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreateBookingCustomAttributeDefinition (buyer-level)","FullyQualifiedName":"CreateBookingCustomAttributeDefinition (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreateBookingCustomAttributeDefinition (seller-level)","FullyQualifiedName":"CreateBookingCustomAttributeDefinition (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_WRITE","Parent":null}},{"Resource":{"Name":"CreateBookingCustomAttributeDefinition (seller-level)","FullyQualifiedName":"CreateBookingCustomAttributeDefinition (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreateBreakType","FullyQualifiedName":"CreateBreakType","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_SETTINGS_WRITE","Parent":null}},{"Resource":{"Name":"CreateCard","FullyQualifiedName":"CreateCard","Type":"endpoint","Metadata":null,"Parent":{"Name":"Cards","FullyQualifiedName":"Cards","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreateCatalogImage","FullyQualifiedName":"CreateCatalogImage","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_WRITE","Parent":null}},{"Resource":{"Name":"CreateCustomer","FullyQualifiedName":"CreateCustomer","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"CreateCustomerCard (deprecated)","FullyQualifiedName":"CreateCustomerCard (deprecated)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"CreateCustomerCustomAttributeDefinition","FullyQualifiedName":"CreateCustomerCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Custom Attributes","FullyQualifiedName":"Customer Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"CreateCustomerGroup","FullyQualifiedName":"CreateCustomerGroup","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Groups","FullyQualifiedName":"Customer Groups","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"CreateDeviceCode","FullyQualifiedName":"CreateDeviceCode","Type":"endpoint","Metadata":null,"Parent":{"Name":"Devices","FullyQualifiedName":"Devices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DEVICE_CREDENTIAL_MANAGEMENT","Parent":null}},{"Resource":{"Name":"CreateDisputeEvidenceFile","FullyQualifiedName":"CreateDisputeEvidenceFile","Type":"endpoint","Metadata":null,"Parent":{"Name":"Disputes","FullyQualifiedName":"Disputes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DISPUTES_WRITE","Parent":null}},{"Resource":{"Name":"CreateDisputeEvidenceText","FullyQualifiedName":"CreateDisputeEvidenceText","Type":"endpoint","Metadata":null,"Parent":{"Name":"Disputes","FullyQualifiedName":"Disputes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DISPUTES_WRITE","Parent":null}},{"Resource":{"Name":"CreateGiftCard","FullyQualifiedName":"CreateGiftCard","Type":"endpoint","Metadata":null,"Parent":{"Name":"Gift Cards","FullyQualifiedName":"Gift Cards","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"GIFTCARDS_WRITE","Parent":null}},{"Resource":{"Name":"CreateGiftCardActivity","FullyQualifiedName":"CreateGiftCardActivity","Type":"endpoint","Metadata":null,"Parent":{"Name":"Gift Card Activities","FullyQualifiedName":"Gift Card Activities","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"GIFTCARDS_WRITE","Parent":null}},{"Resource":{"Name":"CreateInvoice","FullyQualifiedName":"CreateInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"CreateInvoice","FullyQualifiedName":"CreateInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"CreateInvoiceAttachment","FullyQualifiedName":"CreateInvoiceAttachment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"CreateInvoiceAttachment","FullyQualifiedName":"CreateInvoiceAttachment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"CreateLocation","FullyQualifiedName":"CreateLocation","Type":"endpoint","Metadata":null,"Parent":{"Name":"Locations","FullyQualifiedName":"Locations","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"CreateLocationCustomAttributeDefinition","FullyQualifiedName":"CreateLocationCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Location Custom Attributes","FullyQualifiedName":"Location Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"CreateLoyaltyAccount","FullyQualifiedName":"CreateLoyaltyAccount","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_WRITE","Parent":null}},{"Resource":{"Name":"CreateLoyaltyPromotion","FullyQualifiedName":"CreateLoyaltyPromotion","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_WRITE","Parent":null}},{"Resource":{"Name":"CreateLoyaltyReward","FullyQualifiedName":"CreateLoyaltyReward","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_WRITE","Parent":null}},{"Resource":{"Name":"CreateMerchantCustomAttributeDefinition","FullyQualifiedName":"CreateMerchantCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchant Custom Attributes","FullyQualifiedName":"Merchant Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"CreateMobileAuthorizationCode","FullyQualifiedName":"CreateMobileAuthorizationCode","Type":"endpoint","Metadata":null,"Parent":{"Name":"Mobile Authorization","FullyQualifiedName":"Mobile Authorization","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE_IN_PERSON","Parent":null}},{"Resource":{"Name":"CreateOrder","FullyQualifiedName":"CreateOrder","Type":"endpoint","Metadata":null,"Parent":{"Name":"Orders","FullyQualifiedName":"Orders","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"CreateOrderCustomAttributeDefinition","FullyQualifiedName":"CreateOrderCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Order Custom Attributes","FullyQualifiedName":"Order Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"CreatePayment","FullyQualifiedName":"CreatePayment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreatePayment","FullyQualifiedName":"CreatePayment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE_ADDITIONAL_RECIPIENTS","Parent":null}},{"Resource":{"Name":"CreatePayment","FullyQualifiedName":"CreatePayment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE_SHARED_ONFILE","Parent":null}},{"Resource":{"Name":"CreatePaymentLink","FullyQualifiedName":"CreatePaymentLink","Type":"endpoint","Metadata":null,"Parent":{"Name":"Checkout","FullyQualifiedName":"Checkout","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_READ","Parent":null}},{"Resource":{"Name":"CreatePaymentLink","FullyQualifiedName":"CreatePaymentLink","Type":"endpoint","Metadata":null,"Parent":{"Name":"Checkout","FullyQualifiedName":"Checkout","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"CreatePaymentLink","FullyQualifiedName":"CreatePaymentLink","Type":"endpoint","Metadata":null,"Parent":{"Name":"Checkout","FullyQualifiedName":"Checkout","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreateShift","FullyQualifiedName":"CreateShift","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_WRITE","Parent":null}},{"Resource":{"Name":"CreateSubscription","FullyQualifiedName":"CreateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"CreateSubscription","FullyQualifiedName":"CreateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"CreateSubscription","FullyQualifiedName":"CreateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_READ","Parent":null}},{"Resource":{"Name":"CreateSubscription","FullyQualifiedName":"CreateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"CreateSubscription","FullyQualifiedName":"CreateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreateSubscription","FullyQualifiedName":"CreateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"SUBSCRIPTIONS_WRITE","Parent":null}},{"Resource":{"Name":"CreateTeamMember","FullyQualifiedName":"CreateTeamMember","Type":"endpoint","Metadata":null,"Parent":{"Name":"Team","FullyQualifiedName":"Team","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_WRITE","Parent":null}},{"Resource":{"Name":"CreateTerminalAction","FullyQualifiedName":"CreateTerminalAction","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreateTerminalCheckout","FullyQualifiedName":"CreateTerminalCheckout","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreateTerminalRefund","FullyQualifiedName":"CreateTerminalRefund","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"CreateVendor","FullyQualifiedName":"CreateVendor","Type":"endpoint","Metadata":null,"Parent":{"Name":"Vendors","FullyQualifiedName":"Vendors","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"VENDOR_WRITE","Parent":null}},{"Resource":{"Name":"DeleteBookingCustomAttribute (buyer-level)","FullyQualifiedName":"DeleteBookingCustomAttribute (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteBookingCustomAttribute (seller-level)","FullyQualifiedName":"DeleteBookingCustomAttribute (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_WRITE","Parent":null}},{"Resource":{"Name":"DeleteBookingCustomAttribute (seller-level)","FullyQualifiedName":"DeleteBookingCustomAttribute (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteBookingCustomAttributeDefinition (buyer-level)","FullyQualifiedName":"DeleteBookingCustomAttributeDefinition (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteBookingCustomAttributeDefinition (seller-level)","FullyQualifiedName":"DeleteBookingCustomAttributeDefinition (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_WRITE","Parent":null}},{"Resource":{"Name":"DeleteBookingCustomAttributeDefinition (seller-level)","FullyQualifiedName":"DeleteBookingCustomAttributeDefinition (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteBreakType","FullyQualifiedName":"DeleteBreakType","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_SETTINGS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteCatalogObject","FullyQualifiedName":"DeleteCatalogObject","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteCustomer","FullyQualifiedName":"DeleteCustomer","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteCustomerCard (deprecated)","FullyQualifiedName":"DeleteCustomerCard (deprecated)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteCustomerCustomAttribute","FullyQualifiedName":"DeleteCustomerCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Custom Attributes","FullyQualifiedName":"Customer Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteCustomerCustomAttributeDefinition","FullyQualifiedName":"DeleteCustomerCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Custom Attributes","FullyQualifiedName":"Customer Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteCustomerGroup","FullyQualifiedName":"DeleteCustomerGroup","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Groups","FullyQualifiedName":"Customer Groups","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteDisputeEvidence","FullyQualifiedName":"DeleteDisputeEvidence","Type":"endpoint","Metadata":null,"Parent":{"Name":"Disputes","FullyQualifiedName":"Disputes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DISPUTES_WRITE","Parent":null}},{"Resource":{"Name":"DeleteInvoice","FullyQualifiedName":"DeleteInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"DeleteInvoice","FullyQualifiedName":"DeleteInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteInvoiceAttachment","FullyQualifiedName":"DeleteInvoiceAttachment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"DeleteInvoiceAttachment","FullyQualifiedName":"DeleteInvoiceAttachment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteLocationCustomAttribute","FullyQualifiedName":"DeleteLocationCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Location Custom Attributes","FullyQualifiedName":"Location Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"DeleteLocationCustomAttributeDefinition","FullyQualifiedName":"DeleteLocationCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Location Custom Attributes","FullyQualifiedName":"Location Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"DeleteLoyaltyReward","FullyQualifiedName":"DeleteLoyaltyReward","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_WRITE","Parent":null}},{"Resource":{"Name":"DeleteMerchantCustomAttribute","FullyQualifiedName":"DeleteMerchantCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchant Custom Attributes","FullyQualifiedName":"Merchant Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"DeleteMerchantCustomAttributeDefinition","FullyQualifiedName":"DeleteMerchantCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchant Custom Attributes","FullyQualifiedName":"Merchant Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"DeleteOrderCustomAttribute","FullyQualifiedName":"DeleteOrderCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Order Custom Attributes","FullyQualifiedName":"Order Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteOrderCustomAttributeDefinition","FullyQualifiedName":"DeleteOrderCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Order Custom Attributes","FullyQualifiedName":"Order Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteShift","FullyQualifiedName":"DeleteShift","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteSnippet","FullyQualifiedName":"DeleteSnippet","Type":"endpoint","Metadata":null,"Parent":{"Name":"Snippets","FullyQualifiedName":"Snippets","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ONLINE_STORE_SNIPPETS_WRITE","Parent":null}},{"Resource":{"Name":"DeleteSubscriptionAction","FullyQualifiedName":"DeleteSubscriptionAction","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"SUBSCRIPTIONS_WRITE","Parent":null}},{"Resource":{"Name":"DisableCard","FullyQualifiedName":"DisableCard","Type":"endpoint","Metadata":null,"Parent":{"Name":"Cards","FullyQualifiedName":"Cards","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"GetBankAccount","FullyQualifiedName":"GetBankAccount","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bank Accounts","FullyQualifiedName":"Bank Accounts","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"BANK_ACCOUNTS_READ","Parent":null}},{"Resource":{"Name":"GetBankAccountByV1Id","FullyQualifiedName":"GetBankAccountByV1Id","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bank Accounts","FullyQualifiedName":"Bank Accounts","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"BANK_ACCOUNTS_READ","Parent":null}},{"Resource":{"Name":"GetBreakType","FullyQualifiedName":"GetBreakType","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_SETTINGS_READ","Parent":null}},{"Resource":{"Name":"GetDevice","FullyQualifiedName":"GetDevice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Devices","FullyQualifiedName":"Devices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DEVICES_READ","Parent":null}},{"Resource":{"Name":"GetDeviceCode","FullyQualifiedName":"GetDeviceCode","Type":"endpoint","Metadata":null,"Parent":{"Name":"Devices","FullyQualifiedName":"Devices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DEVICE_CREDENTIAL_MANAGEMENT","Parent":null}},{"Resource":{"Name":"GetInvoice","FullyQualifiedName":"GetInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_READ","Parent":null}},{"Resource":{"Name":"GetPayment","FullyQualifiedName":"GetPayment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"GetPaymentRefund","FullyQualifiedName":"GetPaymentRefund","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"GetPayout","FullyQualifiedName":"GetPayout","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payouts","FullyQualifiedName":"Payouts","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYOUTS_READ","Parent":null}},{"Resource":{"Name":"GetShift","FullyQualifiedName":"GetShift","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_READ","Parent":null}},{"Resource":{"Name":"GetTeamMemberWage","FullyQualifiedName":"GetTeamMemberWage","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_READ","Parent":null}},{"Resource":{"Name":"GetTerminalAction","FullyQualifiedName":"GetTerminalAction","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"GetTerminalAction","FullyQualifiedName":"GetTerminalAction","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"GetTerminalCheckout","FullyQualifiedName":"GetTerminalCheckout","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"GetTerminalRefund","FullyQualifiedName":"GetTerminalRefund","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"LinkCustomerToGiftCard","FullyQualifiedName":"LinkCustomerToGiftCard","Type":"endpoint","Metadata":null,"Parent":{"Name":"Gift Cards","FullyQualifiedName":"Gift Cards","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"GIFTCARDS_WRITE","Parent":null}},{"Resource":{"Name":"ListBankAccounts","FullyQualifiedName":"ListBankAccounts","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bank Accounts","FullyQualifiedName":"Bank Accounts","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"BANK_ACCOUNTS_READ","Parent":null}},{"Resource":{"Name":"ListBookingCustomAttributeDefinitions (buyer-level)","FullyQualifiedName":"ListBookingCustomAttributeDefinitions (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"ListBookingCustomAttributeDefinitions (seller-level)","FullyQualifiedName":"ListBookingCustomAttributeDefinitions (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_READ","Parent":null}},{"Resource":{"Name":"ListBookingCustomAttributeDefinitions (seller-level)","FullyQualifiedName":"ListBookingCustomAttributeDefinitions (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"ListBookingCustomAttributes (buyer-level)","FullyQualifiedName":"ListBookingCustomAttributes (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"ListBookingCustomAttributes (seller-level)","FullyQualifiedName":"ListBookingCustomAttributes (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_READ","Parent":null}},{"Resource":{"Name":"ListBookingCustomAttributes (seller-level)","FullyQualifiedName":"ListBookingCustomAttributes (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"ListBookings (buyer-level)","FullyQualifiedName":"ListBookings (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"ListBookings (seller-level)","FullyQualifiedName":"ListBookings (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_READ","Parent":null}},{"Resource":{"Name":"ListBookings (seller-level)","FullyQualifiedName":"ListBookings (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"ListBreakTypes","FullyQualifiedName":"ListBreakTypes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_SETTINGS_READ","Parent":null}},{"Resource":{"Name":"ListCards","FullyQualifiedName":"ListCards","Type":"endpoint","Metadata":null,"Parent":{"Name":"Cards","FullyQualifiedName":"Cards","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"ListCashDrawerShiftEvents","FullyQualifiedName":"ListCashDrawerShiftEvents","Type":"endpoint","Metadata":null,"Parent":{"Name":"Cash Drawer Shifts","FullyQualifiedName":"Cash Drawer Shifts","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CASH_DRAWER_READ","Parent":null}},{"Resource":{"Name":"ListCashDrawerShifts","FullyQualifiedName":"ListCashDrawerShifts","Type":"endpoint","Metadata":null,"Parent":{"Name":"Cash Drawer Shifts","FullyQualifiedName":"Cash Drawer Shifts","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CASH_DRAWER_READ","Parent":null}},{"Resource":{"Name":"ListCatalog","FullyQualifiedName":"ListCatalog","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_READ","Parent":null}},{"Resource":{"Name":"ListCustomerCustomAttributeDefinitions","FullyQualifiedName":"ListCustomerCustomAttributeDefinitions","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Custom Attributes","FullyQualifiedName":"Customer Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"ListCustomerCustomAttributes","FullyQualifiedName":"ListCustomerCustomAttributes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Custom Attributes","FullyQualifiedName":"Customer Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"ListCustomerGroups","FullyQualifiedName":"ListCustomerGroups","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Groups","FullyQualifiedName":"Customer Groups","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"ListCustomerSegments","FullyQualifiedName":"ListCustomerSegments","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Segments","FullyQualifiedName":"Customer Segments","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"ListCustomers","FullyQualifiedName":"ListCustomers","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"ListDeviceCodes","FullyQualifiedName":"ListDeviceCodes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Devices","FullyQualifiedName":"Devices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DEVICE_CREDENTIAL_MANAGEMENT","Parent":null}},{"Resource":{"Name":"ListDevices","FullyQualifiedName":"ListDevices","Type":"endpoint","Metadata":null,"Parent":{"Name":"Devices","FullyQualifiedName":"Devices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DEVICES_READ","Parent":null}},{"Resource":{"Name":"ListDisputeEvidence","FullyQualifiedName":"ListDisputeEvidence","Type":"endpoint","Metadata":null,"Parent":{"Name":"Disputes","FullyQualifiedName":"Disputes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DISPUTES_READ","Parent":null}},{"Resource":{"Name":"ListDisputes","FullyQualifiedName":"ListDisputes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Disputes","FullyQualifiedName":"Disputes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DISPUTES_READ","Parent":null}},{"Resource":{"Name":"ListEmployees (deprecated)","FullyQualifiedName":"ListEmployees (deprecated)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Employees","FullyQualifiedName":"Employees","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_READ","Parent":null}},{"Resource":{"Name":"ListGiftCardActivities","FullyQualifiedName":"ListGiftCardActivities","Type":"endpoint","Metadata":null,"Parent":{"Name":"Gift Card Activities","FullyQualifiedName":"Gift Card Activities","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"GIFTCARDS_READ","Parent":null}},{"Resource":{"Name":"ListGiftCards","FullyQualifiedName":"ListGiftCards","Type":"endpoint","Metadata":null,"Parent":{"Name":"Gift Cards","FullyQualifiedName":"Gift Cards","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"GIFTCARDS_READ","Parent":null}},{"Resource":{"Name":"ListInvoices","FullyQualifiedName":"ListInvoices","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_READ","Parent":null}},{"Resource":{"Name":"ListLocationCustomAttributeDefinitions","FullyQualifiedName":"ListLocationCustomAttributeDefinitions","Type":"endpoint","Metadata":null,"Parent":{"Name":"Location Custom Attributes","FullyQualifiedName":"Location Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"ListLocationCustomAttributes","FullyQualifiedName":"ListLocationCustomAttributes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Location Custom Attributes","FullyQualifiedName":"Location Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"ListLocations","FullyQualifiedName":"ListLocations","Type":"endpoint","Metadata":null,"Parent":{"Name":"Locations","FullyQualifiedName":"Locations","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"ListLoyaltyPrograms (deprecated)","FullyQualifiedName":"ListLoyaltyPrograms (deprecated)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_READ","Parent":null}},{"Resource":{"Name":"ListLoyaltyPromotions","FullyQualifiedName":"ListLoyaltyPromotions","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_READ","Parent":null}},{"Resource":{"Name":"ListMerchantCustomAttributeDefinitions","FullyQualifiedName":"ListMerchantCustomAttributeDefinitions","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchant Custom Attributes","FullyQualifiedName":"Merchant Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"ListMerchantCustomAttributes","FullyQualifiedName":"ListMerchantCustomAttributes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchant Custom Attributes","FullyQualifiedName":"Merchant Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"ListMerchants","FullyQualifiedName":"ListMerchants","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchants","FullyQualifiedName":"Merchants","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"ListOrderCustomAttributeDefinitions","FullyQualifiedName":"ListOrderCustomAttributeDefinitions","Type":"endpoint","Metadata":null,"Parent":{"Name":"Order Custom Attributes","FullyQualifiedName":"Order Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_READ","Parent":null}},{"Resource":{"Name":"ListOrderCustomAttributes","FullyQualifiedName":"ListOrderCustomAttributes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Order Custom Attributes","FullyQualifiedName":"Order Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_READ","Parent":null}},{"Resource":{"Name":"ListPaymentRefunds","FullyQualifiedName":"ListPaymentRefunds","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"ListPayments","FullyQualifiedName":"ListPayments","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"ListPayoutEntries","FullyQualifiedName":"ListPayoutEntries","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payouts","FullyQualifiedName":"Payouts","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYOUTS_READ","Parent":null}},{"Resource":{"Name":"ListPayouts","FullyQualifiedName":"ListPayouts","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payouts","FullyQualifiedName":"Payouts","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYOUTS_READ","Parent":null}},{"Resource":{"Name":"ListSites","FullyQualifiedName":"ListSites","Type":"endpoint","Metadata":null,"Parent":{"Name":"Sites","FullyQualifiedName":"Sites","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ONLINE_STORE_SITE_READ","Parent":null}},{"Resource":{"Name":"ListSubscriptionEvents","FullyQualifiedName":"ListSubscriptionEvents","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"SUBSCRIPTIONS_READ","Parent":null}},{"Resource":{"Name":"ListTeamMemberBookingProfiles","FullyQualifiedName":"ListTeamMemberBookingProfiles","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_BUSINESS_SETTINGS_READ","Parent":null}},{"Resource":{"Name":"ListTeamMemberWages","FullyQualifiedName":"ListTeamMemberWages","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_READ","Parent":null}},{"Resource":{"Name":"ListWorkweekConfigs","FullyQualifiedName":"ListWorkweekConfigs","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_SETTINGS_READ","Parent":null}},{"Resource":{"Name":"PauseSubscription","FullyQualifiedName":"PauseSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"PauseSubscription","FullyQualifiedName":"PauseSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"PauseSubscription","FullyQualifiedName":"PauseSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_READ","Parent":null}},{"Resource":{"Name":"PauseSubscription","FullyQualifiedName":"PauseSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"PauseSubscription","FullyQualifiedName":"PauseSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"PauseSubscription","FullyQualifiedName":"PauseSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"SUBSCRIPTIONS_WRITE","Parent":null}},{"Resource":{"Name":"PayOrder","FullyQualifiedName":"PayOrder","Type":"endpoint","Metadata":null,"Parent":{"Name":"Orders","FullyQualifiedName":"Orders","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"PayOrder","FullyQualifiedName":"PayOrder","Type":"endpoint","Metadata":null,"Parent":{"Name":"Orders","FullyQualifiedName":"Orders","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"PublishInvoice","FullyQualifiedName":"PublishInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"PublishInvoice","FullyQualifiedName":"PublishInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"PublishInvoice","FullyQualifiedName":"PublishInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"PublishInvoice","FullyQualifiedName":"PublishInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"RedeemLoyaltyReward","FullyQualifiedName":"RedeemLoyaltyReward","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_WRITE","Parent":null}},{"Resource":{"Name":"RefundPayment","FullyQualifiedName":"RefundPayment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"RefundPayment","FullyQualifiedName":"RefundPayment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Payments and Refunds","FullyQualifiedName":"Payments and Refunds","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE_ADDITIONAL_RECIPIENTS","Parent":null}},{"Resource":{"Name":"RemoveGroupFromCustomer","FullyQualifiedName":"RemoveGroupFromCustomer","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"ResumeSubscription","FullyQualifiedName":"ResumeSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"ResumeSubscription","FullyQualifiedName":"ResumeSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"ResumeSubscription","FullyQualifiedName":"ResumeSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_READ","Parent":null}},{"Resource":{"Name":"ResumeSubscription","FullyQualifiedName":"ResumeSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"ResumeSubscription","FullyQualifiedName":"ResumeSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"ResumeSubscription","FullyQualifiedName":"ResumeSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"SUBSCRIPTIONS_WRITE","Parent":null}},{"Resource":{"Name":"RetrieveBooking (buyer-level)","FullyQualifiedName":"RetrieveBooking (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"RetrieveBooking (seller-level)","FullyQualifiedName":"RetrieveBooking (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_READ","Parent":null}},{"Resource":{"Name":"RetrieveBooking (seller-level)","FullyQualifiedName":"RetrieveBooking (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"RetrieveBookingCustomAttribute (buyer-level)","FullyQualifiedName":"RetrieveBookingCustomAttribute (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"RetrieveBookingCustomAttribute (seller-level)","FullyQualifiedName":"RetrieveBookingCustomAttribute (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_READ","Parent":null}},{"Resource":{"Name":"RetrieveBookingCustomAttribute (seller-level)","FullyQualifiedName":"RetrieveBookingCustomAttribute (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"RetrieveBookingCustomAttributeDefinition (buyer-level)","FullyQualifiedName":"RetrieveBookingCustomAttributeDefinition (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"RetrieveBookingCustomAttributeDefinition (seller-level)","FullyQualifiedName":"RetrieveBookingCustomAttributeDefinition (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_READ","Parent":null}},{"Resource":{"Name":"RetrieveBookingCustomAttributeDefinition (seller-level)","FullyQualifiedName":"RetrieveBookingCustomAttributeDefinition (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"RetrieveBusinessBookingProfile","FullyQualifiedName":"RetrieveBusinessBookingProfile","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_BUSINESS_SETTINGS_READ","Parent":null}},{"Resource":{"Name":"RetrieveCard","FullyQualifiedName":"RetrieveCard","Type":"endpoint","Metadata":null,"Parent":{"Name":"Cards","FullyQualifiedName":"Cards","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"RetrieveCashDrawerShift","FullyQualifiedName":"RetrieveCashDrawerShift","Type":"endpoint","Metadata":null,"Parent":{"Name":"Cash Drawer Shifts","FullyQualifiedName":"Cash Drawer Shifts","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CASH_DRAWER_READ","Parent":null}},{"Resource":{"Name":"RetrieveCatalogObject","FullyQualifiedName":"RetrieveCatalogObject","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_READ","Parent":null}},{"Resource":{"Name":"RetrieveCustomer","FullyQualifiedName":"RetrieveCustomer","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"RetrieveCustomerCustomAttribute","FullyQualifiedName":"RetrieveCustomerCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Custom Attributes","FullyQualifiedName":"Customer Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"RetrieveCustomerCustomAttributeDefinition","FullyQualifiedName":"RetrieveCustomerCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Custom Attributes","FullyQualifiedName":"Customer Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"RetrieveCustomerGroup","FullyQualifiedName":"RetrieveCustomerGroup","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Groups","FullyQualifiedName":"Customer Groups","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"RetrieveCustomerSegment","FullyQualifiedName":"RetrieveCustomerSegment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Segments","FullyQualifiedName":"Customer Segments","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"RetrieveDispute","FullyQualifiedName":"RetrieveDispute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Disputes","FullyQualifiedName":"Disputes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DISPUTES_READ","Parent":null}},{"Resource":{"Name":"RetrieveDisputeEvidence","FullyQualifiedName":"RetrieveDisputeEvidence","Type":"endpoint","Metadata":null,"Parent":{"Name":"Disputes","FullyQualifiedName":"Disputes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DISPUTES_READ","Parent":null}},{"Resource":{"Name":"RetrieveEmployee (deprecated)","FullyQualifiedName":"RetrieveEmployee (deprecated)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Employees","FullyQualifiedName":"Employees","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_READ","Parent":null}},{"Resource":{"Name":"RetrieveGiftCard","FullyQualifiedName":"RetrieveGiftCard","Type":"endpoint","Metadata":null,"Parent":{"Name":"Gift Cards","FullyQualifiedName":"Gift Cards","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"GIFTCARDS_READ","Parent":null}},{"Resource":{"Name":"RetrieveGiftCardFromGAN","FullyQualifiedName":"RetrieveGiftCardFromGAN","Type":"endpoint","Metadata":null,"Parent":{"Name":"Gift Cards","FullyQualifiedName":"Gift Cards","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"GIFTCARDS_READ","Parent":null}},{"Resource":{"Name":"RetrieveGiftCardFromNonce","FullyQualifiedName":"RetrieveGiftCardFromNonce","Type":"endpoint","Metadata":null,"Parent":{"Name":"Gift Cards","FullyQualifiedName":"Gift Cards","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"GIFTCARDS_READ","Parent":null}},{"Resource":{"Name":"RetrieveInventoryAdjustment","FullyQualifiedName":"RetrieveInventoryAdjustment","Type":"endpoint","Metadata":null,"Parent":{"Name":"Inventory","FullyQualifiedName":"Inventory","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVENTORY_READ","Parent":null}},{"Resource":{"Name":"RetrieveInventoryChanges","FullyQualifiedName":"RetrieveInventoryChanges","Type":"endpoint","Metadata":null,"Parent":{"Name":"Inventory","FullyQualifiedName":"Inventory","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVENTORY_READ","Parent":null}},{"Resource":{"Name":"RetrieveInventoryCount","FullyQualifiedName":"RetrieveInventoryCount","Type":"endpoint","Metadata":null,"Parent":{"Name":"Inventory","FullyQualifiedName":"Inventory","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVENTORY_READ","Parent":null}},{"Resource":{"Name":"RetrieveInventoryPhysicalCount","FullyQualifiedName":"RetrieveInventoryPhysicalCount","Type":"endpoint","Metadata":null,"Parent":{"Name":"Inventory","FullyQualifiedName":"Inventory","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVENTORY_READ","Parent":null}},{"Resource":{"Name":"RetrieveLocation","FullyQualifiedName":"RetrieveLocation","Type":"endpoint","Metadata":null,"Parent":{"Name":"Locations","FullyQualifiedName":"Locations","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"RetrieveLocationCustomAttribute","FullyQualifiedName":"RetrieveLocationCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Location Custom Attributes","FullyQualifiedName":"Location Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"RetrieveLocationCustomAttributeDefinition","FullyQualifiedName":"RetrieveLocationCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Location Custom Attributes","FullyQualifiedName":"Location Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"RetrieveLoyaltyAccount","FullyQualifiedName":"RetrieveLoyaltyAccount","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_READ","Parent":null}},{"Resource":{"Name":"RetrieveLoyaltyProgram","FullyQualifiedName":"RetrieveLoyaltyProgram","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_READ","Parent":null}},{"Resource":{"Name":"RetrieveLoyaltyPromotion","FullyQualifiedName":"RetrieveLoyaltyPromotion","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_READ","Parent":null}},{"Resource":{"Name":"RetrieveLoyaltyReward","FullyQualifiedName":"RetrieveLoyaltyReward","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_READ","Parent":null}},{"Resource":{"Name":"RetrieveMerchant","FullyQualifiedName":"RetrieveMerchant","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchants","FullyQualifiedName":"Merchants","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"RetrieveMerchantCustomAttribute","FullyQualifiedName":"RetrieveMerchantCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchant Custom Attributes","FullyQualifiedName":"Merchant Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"RetrieveMerchantCustomAttributeDefinition","FullyQualifiedName":"RetrieveMerchantCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchant Custom Attributes","FullyQualifiedName":"Merchant Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_READ","Parent":null}},{"Resource":{"Name":"RetrieveOrder","FullyQualifiedName":"RetrieveOrder","Type":"endpoint","Metadata":null,"Parent":{"Name":"Orders","FullyQualifiedName":"Orders","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_READ","Parent":null}},{"Resource":{"Name":"RetrieveOrder","FullyQualifiedName":"RetrieveOrder","Type":"endpoint","Metadata":null,"Parent":{"Name":"Orders","FullyQualifiedName":"Orders","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"RetrieveOrderCustomAttribute","FullyQualifiedName":"RetrieveOrderCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Order Custom Attributes","FullyQualifiedName":"Order Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_READ","Parent":null}},{"Resource":{"Name":"RetrieveOrderCustomAttributeDefinition","FullyQualifiedName":"RetrieveOrderCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Order Custom Attributes","FullyQualifiedName":"Order Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_READ","Parent":null}},{"Resource":{"Name":"RetrieveSnippet","FullyQualifiedName":"RetrieveSnippet","Type":"endpoint","Metadata":null,"Parent":{"Name":"Snippets","FullyQualifiedName":"Snippets","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ONLINE_STORE_SNIPPETS_READ","Parent":null}},{"Resource":{"Name":"RetrieveSubscription","FullyQualifiedName":"RetrieveSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"SUBSCRIPTIONS_READ","Parent":null}},{"Resource":{"Name":"RetrieveTeamMember","FullyQualifiedName":"RetrieveTeamMember","Type":"endpoint","Metadata":null,"Parent":{"Name":"Team","FullyQualifiedName":"Team","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_READ","Parent":null}},{"Resource":{"Name":"RetrieveTeamMemberBookingProfile","FullyQualifiedName":"RetrieveTeamMemberBookingProfile","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_BUSINESS_SETTINGS_READ","Parent":null}},{"Resource":{"Name":"RetrieveVendor","FullyQualifiedName":"RetrieveVendor","Type":"endpoint","Metadata":null,"Parent":{"Name":"Vendors","FullyQualifiedName":"Vendors","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"VENDOR_READ","Parent":null}},{"Resource":{"Name":"RetrieveWageSetting","FullyQualifiedName":"RetrieveWageSetting","Type":"endpoint","Metadata":null,"Parent":{"Name":"Team","FullyQualifiedName":"Team","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_READ","Parent":null}},{"Resource":{"Name":"SearchAvailability (buyer-level)","FullyQualifiedName":"SearchAvailability (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"SearchAvailability (seller-level)","FullyQualifiedName":"SearchAvailability (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_READ","Parent":null}},{"Resource":{"Name":"SearchAvailability (seller-level)","FullyQualifiedName":"SearchAvailability (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_READ","Parent":null}},{"Resource":{"Name":"SearchCatalogItems","FullyQualifiedName":"SearchCatalogItems","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_READ","Parent":null}},{"Resource":{"Name":"SearchCatalogObjects","FullyQualifiedName":"SearchCatalogObjects","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_READ","Parent":null}},{"Resource":{"Name":"SearchCustomers","FullyQualifiedName":"SearchCustomers","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"SearchInvoices","FullyQualifiedName":"SearchInvoices","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_READ","Parent":null}},{"Resource":{"Name":"SearchLoyaltyAccounts","FullyQualifiedName":"SearchLoyaltyAccounts","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_READ","Parent":null}},{"Resource":{"Name":"SearchLoyaltyEvents","FullyQualifiedName":"SearchLoyaltyEvents","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_READ","Parent":null}},{"Resource":{"Name":"SearchLoyaltyRewards","FullyQualifiedName":"SearchLoyaltyRewards","Type":"endpoint","Metadata":null,"Parent":{"Name":"Loyalty","FullyQualifiedName":"Loyalty","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"LOYALTY_READ","Parent":null}},{"Resource":{"Name":"SearchOrders","FullyQualifiedName":"SearchOrders","Type":"endpoint","Metadata":null,"Parent":{"Name":"Orders","FullyQualifiedName":"Orders","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_READ","Parent":null}},{"Resource":{"Name":"SearchShifts","FullyQualifiedName":"SearchShifts","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_READ","Parent":null}},{"Resource":{"Name":"SearchSubscriptions","FullyQualifiedName":"SearchSubscriptions","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"SUBSCRIPTIONS_READ","Parent":null}},{"Resource":{"Name":"SearchTeamMembers","FullyQualifiedName":"SearchTeamMembers","Type":"endpoint","Metadata":null,"Parent":{"Name":"Team","FullyQualifiedName":"Team","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_READ","Parent":null}},{"Resource":{"Name":"SearchTerminalAction","FullyQualifiedName":"SearchTerminalAction","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"SearchTerminalCheckouts","FullyQualifiedName":"SearchTerminalCheckouts","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"SearchTerminalRefunds","FullyQualifiedName":"SearchTerminalRefunds","Type":"endpoint","Metadata":null,"Parent":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_READ","Parent":null}},{"Resource":{"Name":"SearchVendors","FullyQualifiedName":"SearchVendors","Type":"endpoint","Metadata":null,"Parent":{"Name":"Vendors","FullyQualifiedName":"Vendors","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"VENDOR_READ","Parent":null}},{"Resource":{"Name":"SubmitEvidence","FullyQualifiedName":"SubmitEvidence","Type":"endpoint","Metadata":null,"Parent":{"Name":"Disputes","FullyQualifiedName":"Disputes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"DISPUTES_WRITE","Parent":null}},{"Resource":{"Name":"SwapPlan","FullyQualifiedName":"SwapPlan","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"SwapPlan","FullyQualifiedName":"SwapPlan","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"SwapPlan","FullyQualifiedName":"SwapPlan","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_READ","Parent":null}},{"Resource":{"Name":"SwapPlan","FullyQualifiedName":"SwapPlan","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"SwapPlan","FullyQualifiedName":"SwapPlan","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"SwapPlan","FullyQualifiedName":"SwapPlan","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"SUBSCRIPTIONS_WRITE","Parent":null}},{"Resource":{"Name":"UnlinkCustomerFromGiftCard","FullyQualifiedName":"UnlinkCustomerFromGiftCard","Type":"endpoint","Metadata":null,"Parent":{"Name":"Gift Cards","FullyQualifiedName":"Gift Cards","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"GIFTCARDS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateBooking (buyer-level)","FullyQualifiedName":"UpdateBooking (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateBooking (seller-level)","FullyQualifiedName":"UpdateBooking (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_WRITE","Parent":null}},{"Resource":{"Name":"UpdateBooking (seller-level)","FullyQualifiedName":"UpdateBooking (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Bookings","FullyQualifiedName":"Bookings","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateBookingCustomAttributeDefinition (buyer-level)","FullyQualifiedName":"UpdateBookingCustomAttributeDefinition (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateBookingCustomAttributeDefinition (seller-level)","FullyQualifiedName":"UpdateBookingCustomAttributeDefinition (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_WRITE","Parent":null}},{"Resource":{"Name":"UpdateBookingCustomAttributeDefinition (seller-level)","FullyQualifiedName":"UpdateBookingCustomAttributeDefinition (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateBreakType","FullyQualifiedName":"UpdateBreakType","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_SETTINGS_READ","Parent":null}},{"Resource":{"Name":"UpdateBreakType","FullyQualifiedName":"UpdateBreakType","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_SETTINGS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateCustomer","FullyQualifiedName":"UpdateCustomer","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customers","FullyQualifiedName":"Customers","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateCustomerCustomAttributeDefinition","FullyQualifiedName":"UpdateCustomerCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Custom Attributes","FullyQualifiedName":"Customer Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateCustomerGroup","FullyQualifiedName":"UpdateCustomerGroup","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Groups","FullyQualifiedName":"Customer Groups","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateInvoice","FullyQualifiedName":"UpdateInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"UpdateInvoice","FullyQualifiedName":"UpdateInvoice","Type":"endpoint","Metadata":null,"Parent":{"Name":"Invoices","FullyQualifiedName":"Invoices","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateItemModifierLists","FullyQualifiedName":"UpdateItemModifierLists","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateItemTaxes","FullyQualifiedName":"UpdateItemTaxes","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateLocation","FullyQualifiedName":"UpdateLocation","Type":"endpoint","Metadata":null,"Parent":{"Name":"Locations","FullyQualifiedName":"Locations","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"UpdateLocationCustomAttributeDefinition","FullyQualifiedName":"UpdateLocationCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Location Custom Attributes","FullyQualifiedName":"Location Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"UpdateMerchantCustomAttributeDefinition","FullyQualifiedName":"UpdateMerchantCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchant Custom Attributes","FullyQualifiedName":"Merchant Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"UpdateOrder","FullyQualifiedName":"UpdateOrder","Type":"endpoint","Metadata":null,"Parent":{"Name":"Orders","FullyQualifiedName":"Orders","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateOrderCustomAttributeDefinition","FullyQualifiedName":"UpdateOrderCustomAttributeDefinition","Type":"endpoint","Metadata":null,"Parent":{"Name":"Order Custom Attributes","FullyQualifiedName":"Order Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateShift","FullyQualifiedName":"UpdateShift","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_READ","Parent":null}},{"Resource":{"Name":"UpdateShift","FullyQualifiedName":"UpdateShift","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateSubscription","FullyQualifiedName":"UpdateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_READ","Parent":null}},{"Resource":{"Name":"UpdateSubscription","FullyQualifiedName":"UpdateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"INVOICES_WRITE","Parent":null}},{"Resource":{"Name":"UpdateSubscription","FullyQualifiedName":"UpdateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_READ","Parent":null}},{"Resource":{"Name":"UpdateSubscription","FullyQualifiedName":"UpdateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateSubscription","FullyQualifiedName":"UpdateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"PAYMENTS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateSubscription","FullyQualifiedName":"UpdateSubscription","Type":"endpoint","Metadata":null,"Parent":{"Name":"Subscriptions","FullyQualifiedName":"Subscriptions","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"SUBSCRIPTIONS_WRITE","Parent":null}},{"Resource":{"Name":"UpdateTeamMember","FullyQualifiedName":"UpdateTeamMember","Type":"endpoint","Metadata":null,"Parent":{"Name":"Team","FullyQualifiedName":"Team","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_WRITE","Parent":null}},{"Resource":{"Name":"UpdateVendors","FullyQualifiedName":"UpdateVendors","Type":"endpoint","Metadata":null,"Parent":{"Name":"Vendors","FullyQualifiedName":"Vendors","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"VENDOR_WRITE","Parent":null}},{"Resource":{"Name":"UpdateWageSetting","FullyQualifiedName":"UpdateWageSetting","Type":"endpoint","Metadata":null,"Parent":{"Name":"Team","FullyQualifiedName":"Team","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"EMPLOYEES_WRITE","Parent":null}},{"Resource":{"Name":"UpdateWorkweekConfig","FullyQualifiedName":"UpdateWorkweekConfig","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_SETTINGS_READ","Parent":null}},{"Resource":{"Name":"UpdateWorkweekConfig","FullyQualifiedName":"UpdateWorkweekConfig","Type":"endpoint","Metadata":null,"Parent":{"Name":"Labor","FullyQualifiedName":"Labor","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"TIMECARDS_SETTINGS_WRITE","Parent":null}},{"Resource":{"Name":"UpsertBookingCustomAttribute (buyer-level)","FullyQualifiedName":"UpsertBookingCustomAttribute (buyer-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"UpsertBookingCustomAttribute (seller-level)","FullyQualifiedName":"UpsertBookingCustomAttribute (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_ALL_WRITE","Parent":null}},{"Resource":{"Name":"UpsertBookingCustomAttribute (seller-level)","FullyQualifiedName":"UpsertBookingCustomAttribute (seller-level)","Type":"endpoint","Metadata":null,"Parent":{"Name":"Booking Custom Attributes","FullyQualifiedName":"Booking Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"APPOINTMENTS_WRITE","Parent":null}},{"Resource":{"Name":"UpsertCatalogObject","FullyQualifiedName":"UpsertCatalogObject","Type":"endpoint","Metadata":null,"Parent":{"Name":"Catalog","FullyQualifiedName":"Catalog","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ITEMS_WRITE","Parent":null}},{"Resource":{"Name":"UpsertCustomerCustomAttribute","FullyQualifiedName":"UpsertCustomerCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Customer Custom Attributes","FullyQualifiedName":"Customer Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"CUSTOMERS_WRITE","Parent":null}},{"Resource":{"Name":"UpsertLocationCustomAttribute","FullyQualifiedName":"UpsertLocationCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Location Custom Attributes","FullyQualifiedName":"Location Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"UpsertMerchantCustomAttribute","FullyQualifiedName":"UpsertMerchantCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Merchant Custom Attributes","FullyQualifiedName":"Merchant Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"MERCHANT_PROFILE_WRITE","Parent":null}},{"Resource":{"Name":"UpsertOrderCustomAttribute","FullyQualifiedName":"UpsertOrderCustomAttribute","Type":"endpoint","Metadata":null,"Parent":{"Name":"Order Custom Attributes","FullyQualifiedName":"Order Custom Attributes","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ORDERS_WRITE","Parent":null}},{"Resource":{"Name":"UpsertSnippet","FullyQualifiedName":"UpsertSnippet","Type":"endpoint","Metadata":null,"Parent":{"Name":"Snippets","FullyQualifiedName":"Snippets","Type":"category","Metadata":null,"Parent":null}},"Permission":{"Value":"ONLINE_STORE_SNIPPETS_WRITE","Parent":null}}],"UnboundedResources":[{"Name":"Truffle Security","FullyQualifiedName":"detectors@trufflesec.com","Type":"team_member","Metadata":{"created_at":"2024-08-19T07:23:17Z","is_owner":true},"Parent":null}],"Metadata":{"client_id":"sq0idp-JqoB3AJCTFtclv4eUkMm_Q","expires_at":"","merchant_id":"ML4DDTXKQNB80"}}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/square/permissions.go b/pkg/analyzer/analyzers/square/permissions.go
deleted file mode 100644
index 2d39deb05870..000000000000
--- a/pkg/analyzer/analyzers/square/permissions.go
+++ /dev/null
@@ -1,271 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package square
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- BankAccountsRead Permission = iota
- AppointmentsWrite Permission = iota
- AppointmentsAllWrite Permission = iota
- AppointmentsRead Permission = iota
- AppointmentsAllRead Permission = iota
- AppointmentsBusinessSettingsRead Permission = iota
- PaymentsRead Permission = iota
- PaymentsWrite Permission = iota
- CashDrawerRead Permission = iota
- ItemsWrite Permission = iota
- ItemsRead Permission = iota
- OrdersWrite Permission = iota
- OrdersRead Permission = iota
- CustomersWrite Permission = iota
- CustomersRead Permission = iota
- DeviceCredentialManagement Permission = iota
- DevicesRead Permission = iota
- DisputesWrite Permission = iota
- DisputesRead Permission = iota
- EmployeesRead Permission = iota
- GiftcardsRead Permission = iota
- GiftcardsWrite Permission = iota
- InventoryWrite Permission = iota
- InventoryRead Permission = iota
- InvoicesWrite Permission = iota
- InvoicesRead Permission = iota
- TimecardsSettingsWrite Permission = iota
- TimecardsWrite Permission = iota
- TimecardsSettingsRead Permission = iota
- TimecardsRead Permission = iota
- MerchantProfileWrite Permission = iota
- MerchantProfileRead Permission = iota
- LoyaltyRead Permission = iota
- LoyaltyWrite Permission = iota
- PaymentsWriteInPerson Permission = iota
- PaymentsWriteSharedOnfile Permission = iota
- PaymentsWriteAdditionalRecipients Permission = iota
- PayoutsRead Permission = iota
- OnlineStoreSiteRead Permission = iota
- OnlineStoreSnippetsWrite Permission = iota
- OnlineStoreSnippetsRead Permission = iota
- SubscriptionsWrite Permission = iota
- SubscriptionsRead Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- BankAccountsRead: "bank_accounts_read",
- AppointmentsWrite: "appointments_write",
- AppointmentsAllWrite: "appointments_all_write",
- AppointmentsRead: "appointments_read",
- AppointmentsAllRead: "appointments_all_read",
- AppointmentsBusinessSettingsRead: "appointments_business_settings_read",
- PaymentsRead: "payments_read",
- PaymentsWrite: "payments_write",
- CashDrawerRead: "cash_drawer_read",
- ItemsWrite: "items_write",
- ItemsRead: "items_read",
- OrdersWrite: "orders_write",
- OrdersRead: "orders_read",
- CustomersWrite: "customers_write",
- CustomersRead: "customers_read",
- DeviceCredentialManagement: "device_credential_management",
- DevicesRead: "devices_read",
- DisputesWrite: "disputes_write",
- DisputesRead: "disputes_read",
- EmployeesRead: "employees_read",
- GiftcardsRead: "giftcards_read",
- GiftcardsWrite: "giftcards_write",
- InventoryWrite: "inventory_write",
- InventoryRead: "inventory_read",
- InvoicesWrite: "invoices_write",
- InvoicesRead: "invoices_read",
- TimecardsSettingsWrite: "timecards_settings_write",
- TimecardsWrite: "timecards_write",
- TimecardsSettingsRead: "timecards_settings_read",
- TimecardsRead: "timecards_read",
- MerchantProfileWrite: "merchant_profile_write",
- MerchantProfileRead: "merchant_profile_read",
- LoyaltyRead: "loyalty_read",
- LoyaltyWrite: "loyalty_write",
- PaymentsWriteInPerson: "payments_write_in_person",
- PaymentsWriteSharedOnfile: "payments_write_shared_onfile",
- PaymentsWriteAdditionalRecipients: "payments_write_additional_recipients",
- PayoutsRead: "payouts_read",
- OnlineStoreSiteRead: "online_store_site_read",
- OnlineStoreSnippetsWrite: "online_store_snippets_write",
- OnlineStoreSnippetsRead: "online_store_snippets_read",
- SubscriptionsWrite: "subscriptions_write",
- SubscriptionsRead: "subscriptions_read",
- }
-
- StringToPermission = map[string]Permission{
- "bank_accounts_read": BankAccountsRead,
- "appointments_write": AppointmentsWrite,
- "appointments_all_write": AppointmentsAllWrite,
- "appointments_read": AppointmentsRead,
- "appointments_all_read": AppointmentsAllRead,
- "appointments_business_settings_read": AppointmentsBusinessSettingsRead,
- "payments_read": PaymentsRead,
- "payments_write": PaymentsWrite,
- "cash_drawer_read": CashDrawerRead,
- "items_write": ItemsWrite,
- "items_read": ItemsRead,
- "orders_write": OrdersWrite,
- "orders_read": OrdersRead,
- "customers_write": CustomersWrite,
- "customers_read": CustomersRead,
- "device_credential_management": DeviceCredentialManagement,
- "devices_read": DevicesRead,
- "disputes_write": DisputesWrite,
- "disputes_read": DisputesRead,
- "employees_read": EmployeesRead,
- "giftcards_read": GiftcardsRead,
- "giftcards_write": GiftcardsWrite,
- "inventory_write": InventoryWrite,
- "inventory_read": InventoryRead,
- "invoices_write": InvoicesWrite,
- "invoices_read": InvoicesRead,
- "timecards_settings_write": TimecardsSettingsWrite,
- "timecards_write": TimecardsWrite,
- "timecards_settings_read": TimecardsSettingsRead,
- "timecards_read": TimecardsRead,
- "merchant_profile_write": MerchantProfileWrite,
- "merchant_profile_read": MerchantProfileRead,
- "loyalty_read": LoyaltyRead,
- "loyalty_write": LoyaltyWrite,
- "payments_write_in_person": PaymentsWriteInPerson,
- "payments_write_shared_onfile": PaymentsWriteSharedOnfile,
- "payments_write_additional_recipients": PaymentsWriteAdditionalRecipients,
- "payouts_read": PayoutsRead,
- "online_store_site_read": OnlineStoreSiteRead,
- "online_store_snippets_write": OnlineStoreSnippetsWrite,
- "online_store_snippets_read": OnlineStoreSnippetsRead,
- "subscriptions_write": SubscriptionsWrite,
- "subscriptions_read": SubscriptionsRead,
- }
-
- PermissionIDs = map[Permission]int{
- BankAccountsRead: 1,
- AppointmentsWrite: 2,
- AppointmentsAllWrite: 3,
- AppointmentsRead: 4,
- AppointmentsAllRead: 5,
- AppointmentsBusinessSettingsRead: 6,
- PaymentsRead: 7,
- PaymentsWrite: 8,
- CashDrawerRead: 9,
- ItemsWrite: 10,
- ItemsRead: 11,
- OrdersWrite: 12,
- OrdersRead: 13,
- CustomersWrite: 14,
- CustomersRead: 15,
- DeviceCredentialManagement: 16,
- DevicesRead: 17,
- DisputesWrite: 18,
- DisputesRead: 19,
- EmployeesRead: 20,
- GiftcardsRead: 21,
- GiftcardsWrite: 22,
- InventoryWrite: 23,
- InventoryRead: 24,
- InvoicesWrite: 25,
- InvoicesRead: 26,
- TimecardsSettingsWrite: 27,
- TimecardsWrite: 28,
- TimecardsSettingsRead: 29,
- TimecardsRead: 30,
- MerchantProfileWrite: 31,
- MerchantProfileRead: 32,
- LoyaltyRead: 33,
- LoyaltyWrite: 34,
- PaymentsWriteInPerson: 35,
- PaymentsWriteSharedOnfile: 36,
- PaymentsWriteAdditionalRecipients: 37,
- PayoutsRead: 38,
- OnlineStoreSiteRead: 39,
- OnlineStoreSnippetsWrite: 40,
- OnlineStoreSnippetsRead: 41,
- SubscriptionsWrite: 42,
- SubscriptionsRead: 43,
- }
-
- IdToPermission = map[int]Permission{
- 1: BankAccountsRead,
- 2: AppointmentsWrite,
- 3: AppointmentsAllWrite,
- 4: AppointmentsRead,
- 5: AppointmentsAllRead,
- 6: AppointmentsBusinessSettingsRead,
- 7: PaymentsRead,
- 8: PaymentsWrite,
- 9: CashDrawerRead,
- 10: ItemsWrite,
- 11: ItemsRead,
- 12: OrdersWrite,
- 13: OrdersRead,
- 14: CustomersWrite,
- 15: CustomersRead,
- 16: DeviceCredentialManagement,
- 17: DevicesRead,
- 18: DisputesWrite,
- 19: DisputesRead,
- 20: EmployeesRead,
- 21: GiftcardsRead,
- 22: GiftcardsWrite,
- 23: InventoryWrite,
- 24: InventoryRead,
- 25: InvoicesWrite,
- 26: InvoicesRead,
- 27: TimecardsSettingsWrite,
- 28: TimecardsWrite,
- 29: TimecardsSettingsRead,
- 30: TimecardsRead,
- 31: MerchantProfileWrite,
- 32: MerchantProfileRead,
- 33: LoyaltyRead,
- 34: LoyaltyWrite,
- 35: PaymentsWriteInPerson,
- 36: PaymentsWriteSharedOnfile,
- 37: PaymentsWriteAdditionalRecipients,
- 38: PayoutsRead,
- 39: OnlineStoreSiteRead,
- 40: OnlineStoreSnippetsWrite,
- 41: OnlineStoreSnippetsRead,
- 42: SubscriptionsWrite,
- 43: SubscriptionsRead,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/square/permissions.yaml b/pkg/analyzer/analyzers/square/permissions.yaml
deleted file mode 100644
index 2a60504981e0..000000000000
--- a/pkg/analyzer/analyzers/square/permissions.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-permissions:
- - bank_accounts_read
- - appointments_write
- - appointments_all_write
- - appointments_read
- - appointments_all_read
- - appointments_business_settings_read
- - payments_read
- - payments_write
- - cash_drawer_read
- - items_write
- - items_read
- - orders_write
- - orders_read
- - customers_write
- - customers_read
- - device_credential_management
- - devices_read
- - disputes_write
- - disputes_read
- - employees_read
- - giftcards_read
- - giftcards_write
- - inventory_write
- - inventory_read
- - invoices_write
- - invoices_read
- - timecards_settings_write
- - timecards_write
- - timecards_settings_read
- - timecards_read
- - merchant_profile_write
- - merchant_profile_read
- - loyalty_read
- - loyalty_write
- - payments_write_in_person
- - payments_write_shared_onfile
- - payments_write_additional_recipients
- - payouts_read
- - online_store_site_read
- - online_store_snippets_write
- - online_store_snippets_read
- - subscriptions_write
- - subscriptions_read
diff --git a/pkg/analyzer/analyzers/square/scopes.go b/pkg/analyzer/analyzers/square/scopes.go
deleted file mode 100644
index 5473b6b79edb..000000000000
--- a/pkg/analyzer/analyzers/square/scopes.go
+++ /dev/null
@@ -1,408 +0,0 @@
-package square
-
-var permissions_slice = []map[string]map[string][]string{
- {
- "Bank Accounts": {
- "GetBankAccount": []string{"BANK_ACCOUNTS_READ"},
- "ListBankAccounts": []string{"BANK_ACCOUNTS_READ"},
- "GetBankAccountByV1Id": []string{"BANK_ACCOUNTS_READ"},
- },
- },
- {
- "Bookings": {
- "CreateBooking (buyer-level)": []string{"APPOINTMENTS_WRITE"},
- "CreateBooking (seller-level)": []string{"APPOINTMENTS_WRITE", "APPOINTMENTS_ALL_WRITE"},
- "SearchAvailability (buyer-level)": []string{"APPOINTMENTS_READ"},
- "SearchAvailability (seller-level)": []string{"APPOINTMENTS_READ", "APPOINTMENTS_ALL_READ"},
- "RetrieveBusinessBookingProfile": []string{"APPOINTMENTS_BUSINESS_SETTINGS_READ"},
- "ListTeamMemberBookingProfiles": []string{"APPOINTMENTS_BUSINESS_SETTINGS_READ"},
- "RetrieveTeamMemberBookingProfile": []string{"APPOINTMENTS_BUSINESS_SETTINGS_READ"},
- "ListBookings (buyer-level)": []string{"APPOINTMENTS_READ"},
- "ListBookings (seller-level)": []string{"APPOINTMENTS_READ", "APPOINTMENTS_ALL_READ"},
- "RetrieveBooking (buyer-level)": []string{"APPOINTMENTS_READ"},
- "RetrieveBooking (seller-level)": []string{"APPOINTMENTS_READ", "APPOINTMENTS_ALL_READ"},
- "UpdateBooking (buyer-level)": []string{"APPOINTMENTS_WRITE"},
- "UpdateBooking (seller-level)": []string{"APPOINTMENTS_WRITE", "APPOINTMENTS_ALL_WRITE"},
- "CancelBooking (buyer-level)": []string{"APPOINTMENTS_WRITE"},
- "CancelBooking (seller-level)": []string{"APPOINTMENTS_WRITE", "APPOINTMENTS_ALL_WRITE"},
- },
- },
- {
- "Booking Custom Attributes": {
- "CreateBookingCustomAttributeDefinition (buyer-level)": []string{"APPOINTMENTS_WRITE"},
- "CreateBookingCustomAttributeDefinition (seller-level)": []string{"APPOINTMENTS_WRITE", "APPOINTMENTS_ALL_WRITE"},
- "UpdateBookingCustomAttributeDefinition (buyer-level)": []string{"APPOINTMENTS_WRITE"},
- "UpdateBookingCustomAttributeDefinition (seller-level)": []string{"APPOINTMENTS_WRITE", "APPOINTMENTS_ALL_WRITE"},
- "ListBookingCustomAttributeDefinitions (buyer-level)": []string{"APPOINTMENTS_READ"},
- "ListBookingCustomAttributeDefinitions (seller-level)": []string{"APPOINTMENTS_READ", "APPOINTMENTS_ALL_READ"},
- "RetrieveBookingCustomAttributeDefinition (buyer-level)": []string{"APPOINTMENTS_READ"},
- "RetrieveBookingCustomAttributeDefinition (seller-level)": []string{"APPOINTMENTS_READ", "APPOINTMENTS_ALL_READ"},
- "DeleteBookingCustomAttributeDefinition (buyer-level)": []string{"APPOINTMENTS_WRITE"},
- "DeleteBookingCustomAttributeDefinition (seller-level)": []string{"APPOINTMENTS_WRITE", "APPOINTMENTS_ALL_WRITE"},
- "UpsertBookingCustomAttribute (buyer-level)": []string{"APPOINTMENTS_WRITE"},
- "UpsertBookingCustomAttribute (seller-level)": []string{"APPOINTMENTS_WRITE", "APPOINTMENTS_ALL_WRITE"},
- "BulkUpsertBookingCustomAttributes (buyer-level)": []string{"APPOINTMENTS_WRITE"},
- "BulkUpsertBookingCustomAttributes (seller-level)": []string{"APPOINTMENTS_WRITE", "APPOINTMENTS_ALL_WRITE"},
- "ListBookingCustomAttributes (buyer-level)": []string{"APPOINTMENTS_READ"},
- "ListBookingCustomAttributes (seller-level)": []string{"APPOINTMENTS_READ", "APPOINTMENTS_ALL_READ"},
- "RetrieveBookingCustomAttribute (buyer-level)": []string{"APPOINTMENTS_READ"},
- "RetrieveBookingCustomAttribute (seller-level)": []string{"APPOINTMENTS_READ", "APPOINTMENTS_ALL_READ"},
- "DeleteBookingCustomAttribute (buyer-level)": []string{"APPOINTMENTS_WRITE"},
- "DeleteBookingCustomAttribute (seller-level)": []string{"APPOINTMENTS_WRITE", "APPOINTMENTS_ALL_WRITE"},
- },
- },
- {
- "Cards": {
- "ListCards": []string{"PAYMENTS_READ"},
- "CreateCard": []string{"PAYMENTS_WRITE"},
- "RetrieveCard": []string{"PAYMENTS_READ"},
- "DisableCard": []string{"PAYMENTS_WRITE"},
- },
- },
- {
- "Cash Drawer Shifts": {
- "ListCashDrawerShifts": []string{"CASH_DRAWER_READ"},
- "ListCashDrawerShiftEvents": []string{"CASH_DRAWER_READ"},
- "RetrieveCashDrawerShift": []string{"CASH_DRAWER_READ"},
- },
- },
- {
- "Catalog": {
- "BatchDeleteCatalogObjects": []string{"ITEMS_WRITE"},
- "BatchUpsertCatalogObjects": []string{"ITEMS_WRITE"},
- "BatchRetrieveCatalogObjects": []string{"ITEMS_READ"},
- "CatalogInfo": []string{"ITEMS_READ"},
- "CreateCatalogImage": []string{"ITEMS_WRITE"},
- "DeleteCatalogObject": []string{"ITEMS_WRITE"},
- "ListCatalog": []string{"ITEMS_READ"},
- "RetrieveCatalogObject": []string{"ITEMS_READ"},
- "SearchCatalogItems": []string{"ITEMS_READ"},
- "SearchCatalogObjects": []string{"ITEMS_READ"},
- "UpdateItemTaxes": []string{"ITEMS_WRITE"},
- "UpdateItemModifierLists": []string{"ITEMS_WRITE"},
- "UpsertCatalogObject": []string{"ITEMS_WRITE"},
- },
- },
- {
- "Checkout": {
- "CreatePaymentLink": []string{"ORDERS_WRITE", "ORDERS_READ", "PAYMENTS_WRITE"},
- },
- },
- {
- "Customers": {
- "AddGroupToCustomer": []string{"CUSTOMERS_WRITE"},
- "BulkCreateCustomers": []string{"CUSTOMERS_WRITE"},
- "BulkDeleteCustomers": []string{"CUSTOMERS_WRITE"},
- "BulkRetrieveCustomers": []string{"CUSTOMERS_READ"},
- "BulkUpdateCustomers": []string{"CUSTOMERS_WRITE"},
- "CreateCustomer": []string{"CUSTOMERS_WRITE"},
- "CreateCustomerCard (deprecated)": []string{"CUSTOMERS_WRITE"},
- "DeleteCustomer": []string{"CUSTOMERS_WRITE"},
- "DeleteCustomerCard (deprecated)": []string{"CUSTOMERS_WRITE"},
- "ListCustomers": []string{"CUSTOMERS_READ"},
- "RemoveGroupFromCustomer": []string{"CUSTOMERS_WRITE"},
- "RetrieveCustomer": []string{"CUSTOMERS_READ"},
- "SearchCustomers": []string{"CUSTOMERS_READ"},
- "UpdateCustomer": []string{"CUSTOMERS_WRITE"},
- },
- },
- {
- "Customer Custom Attributes": {
- "CreateCustomerCustomAttributeDefinition": []string{"CUSTOMERS_WRITE"},
- "UpdateCustomerCustomAttributeDefinition": []string{"CUSTOMERS_WRITE"},
- "ListCustomerCustomAttributeDefinitions": []string{"CUSTOMERS_READ"},
- "RetrieveCustomerCustomAttributeDefinition": []string{"CUSTOMERS_READ"},
- "DeleteCustomerCustomAttributeDefinition": []string{"CUSTOMERS_WRITE"},
- "UpsertCustomerCustomAttribute": []string{"CUSTOMERS_WRITE"},
- "BulkUpsertCustomerCustomAttributes": []string{"CUSTOMERS_WRITE"},
- "ListCustomerCustomAttributes": []string{"CUSTOMERS_READ"},
- "RetrieveCustomerCustomAttribute": []string{"CUSTOMERS_READ"},
- "DeleteCustomerCustomAttribute": []string{"CUSTOMERS_WRITE"},
- },
- },
- {
- "Customer Groups": {
- "CreateCustomerGroup": []string{"CUSTOMERS_WRITE"},
- "DeleteCustomerGroup": []string{"CUSTOMERS_WRITE"},
- "ListCustomerGroups": []string{"CUSTOMERS_READ"},
- "RetrieveCustomerGroup": []string{"CUSTOMERS_READ"},
- "UpdateCustomerGroup": []string{"CUSTOMERS_WRITE"},
- },
- },
- {
- "Customer Segments": {
- "ListCustomerSegments": []string{"CUSTOMERS_READ"},
- "RetrieveCustomerSegment": []string{"CUSTOMERS_READ"},
- },
- },
- {
- "Devices": {
- "CreateDeviceCode": []string{"DEVICE_CREDENTIAL_MANAGEMENT"},
- "GetDeviceCode": []string{"DEVICE_CREDENTIAL_MANAGEMENT"},
- "ListDeviceCodes": []string{"DEVICE_CREDENTIAL_MANAGEMENT"},
- "ListDevices": []string{"DEVICES_READ"},
- "GetDevice": []string{"DEVICES_READ"},
- },
- },
- {
- "Disputes": {
- "AcceptDispute": []string{"DISPUTES_WRITE"},
- "CreateDisputeEvidenceFile": []string{"DISPUTES_WRITE"},
- "CreateDisputeEvidenceText": []string{"DISPUTES_WRITE"},
- "ListDisputeEvidence": []string{"DISPUTES_READ"},
- "ListDisputes": []string{"DISPUTES_READ"},
- "DeleteDisputeEvidence": []string{"DISPUTES_WRITE"},
- "RetrieveDispute": []string{"DISPUTES_READ"},
- "RetrieveDisputeEvidence": []string{"DISPUTES_READ"},
- "SubmitEvidence": []string{"DISPUTES_WRITE"},
- },
- },
- {
- "Employees": {
- "ListEmployees (deprecated)": []string{"EMPLOYEES_READ"},
- "RetrieveEmployee (deprecated)": []string{"EMPLOYEES_READ"},
- },
- },
- {
- "Gift Cards": {
- "ListGiftCards": []string{"GIFTCARDS_READ"},
- "CreateGiftCard": []string{"GIFTCARDS_WRITE"},
- "RetrieveGiftCard": []string{"GIFTCARDS_READ"},
- "RetrieveGiftCardFromGAN": []string{"GIFTCARDS_READ"},
- "RetrieveGiftCardFromNonce": []string{"GIFTCARDS_READ"},
- "LinkCustomerToGiftCard": []string{"GIFTCARDS_WRITE"},
- "UnlinkCustomerFromGiftCard": []string{"GIFTCARDS_WRITE"},
- },
- },
- {
- "Gift Card Activities": {
- "ListGiftCardActivities": []string{"GIFTCARDS_READ"},
- "CreateGiftCardActivity": []string{"GIFTCARDS_WRITE"},
- },
- },
- {
- "Inventory": {
- "BatchChangeInventory": []string{"INVENTORY_WRITE"},
- "BatchRetrieveInventoryCounts": []string{"INVENTORY_READ"},
- "BatchRetrieveInventoryChanges": []string{"INVENTORY_READ"},
- "RetrieveInventoryAdjustment": []string{"INVENTORY_READ"},
- "RetrieveInventoryChanges": []string{"INVENTORY_READ"},
- "RetrieveInventoryCount": []string{"INVENTORY_READ"},
- "RetrieveInventoryPhysicalCount": []string{"INVENTORY_READ"},
- },
- },
- {
- "Invoices": {
- "CreateInvoice": []string{"INVOICES_WRITE", "ORDERS_WRITE"},
- "PublishInvoice": []string{"CUSTOMERS_READ", "PAYMENTS_WRITE", "INVOICES_WRITE", "ORDERS_WRITE"},
- "GetInvoice": []string{"INVOICES_READ"},
- "ListInvoices": []string{"INVOICES_READ"},
- "SearchInvoices": []string{"INVOICES_READ"},
- "CreateInvoiceAttachment": []string{"INVOICES_WRITE", "ORDERS_WRITE"},
- "DeleteInvoiceAttachment": []string{"INVOICES_WRITE", "ORDERS_WRITE"},
- "UpdateInvoice": []string{"INVOICES_WRITE", "ORDERS_WRITE"},
- "DeleteInvoice": []string{"INVOICES_WRITE", "ORDERS_WRITE"},
- "CancelInvoice": []string{"INVOICES_WRITE", "ORDERS_WRITE"},
- },
- },
- {
- "Labor": {
- "CreateBreakType": []string{"TIMECARDS_SETTINGS_WRITE"},
- "CreateShift": []string{"TIMECARDS_WRITE"},
- "DeleteBreakType": []string{"TIMECARDS_SETTINGS_WRITE"},
- "DeleteShift": []string{"TIMECARDS_WRITE"},
- "GetBreakType": []string{"TIMECARDS_SETTINGS_READ"},
- "GetTeamMemberWage": []string{"EMPLOYEES_READ"},
- "GetShift": []string{"TIMECARDS_READ"},
- "ListBreakTypes": []string{"TIMECARDS_SETTINGS_READ"},
- "ListTeamMemberWages": []string{"EMPLOYEES_READ"},
- "ListWorkweekConfigs": []string{"TIMECARDS_SETTINGS_READ"},
- "SearchShifts": []string{"TIMECARDS_READ"},
- "UpdateShift": []string{"TIMECARDS_WRITE", "TIMECARDS_READ"},
- "UpdateWorkweekConfig": []string{"TIMECARDS_SETTINGS_WRITE", "TIMECARDS_SETTINGS_READ"},
- "UpdateBreakType": []string{"TIMECARDS_SETTINGS_WRITE", "TIMECARDS_SETTINGS_READ"},
- },
- },
- {
- "Locations": {
- "CreateLocation": []string{"MERCHANT_PROFILE_WRITE"},
- "ListLocations": []string{"MERCHANT_PROFILE_READ"},
- "RetrieveLocation": []string{"MERCHANT_PROFILE_READ"},
- "UpdateLocation": []string{"MERCHANT_PROFILE_WRITE"},
- },
- },
- {
- "Location Custom Attributes": {
- "CreateLocationCustomAttributeDefinition": []string{"MERCHANT_PROFILE_WRITE"},
- "UpdateLocationCustomAttributeDefinition": []string{"MERCHANT_PROFILE_WRITE"},
- "ListLocationCustomAttributeDefinitions": []string{"MERCHANT_PROFILE_READ"},
- "RetrieveLocationCustomAttributeDefinition": []string{"MERCHANT_PROFILE_READ"},
- "DeleteLocationCustomAttributeDefinition": []string{"MERCHANT_PROFILE_WRITE"},
- "UpsertLocationCustomAttribute": []string{"MERCHANT_PROFILE_WRITE"},
- "BulkUpsertLocationCustomAttributes": []string{"MERCHANT_PROFILE_WRITE"},
- "ListLocationCustomAttributes": []string{"MERCHANT_PROFILE_READ"},
- "RetrieveLocationCustomAttribute": []string{"MERCHANT_PROFILE_READ"},
- "DeleteLocationCustomAttribute": []string{"MERCHANT_PROFILE_WRITE"},
- "BulkDeleteLocationCustomAttributes": []string{"MERCHANT_PROFILE_WRITE"},
- },
- },
- {
- "Loyalty": {
- "RetrieveLoyaltyProgram": []string{"LOYALTY_READ"},
- "ListLoyaltyPrograms (deprecated)": []string{"LOYALTY_READ"},
- "CreateLoyaltyPromotion": []string{"LOYALTY_WRITE"},
- "ListLoyaltyPromotions": []string{"LOYALTY_READ"},
- "RetrieveLoyaltyPromotion": []string{"LOYALTY_READ"},
- "CancelLoyaltyPromotion": []string{"LOYALTY_WRITE"},
- "CreateLoyaltyAccount": []string{"LOYALTY_WRITE"},
- "RetrieveLoyaltyAccount": []string{"LOYALTY_READ"},
- "SearchLoyaltyAccounts": []string{"LOYALTY_READ"},
- "AccumulateLoyaltyPoints": []string{"LOYALTY_WRITE"},
- "AdjustLoyaltyPoints": []string{"LOYALTY_WRITE"},
- "CalculateLoyaltyPoints": []string{"LOYALTY_READ"},
- "CreateLoyaltyReward": []string{"LOYALTY_WRITE"},
- "RedeemLoyaltyReward": []string{"LOYALTY_WRITE"},
- "RetrieveLoyaltyReward": []string{"LOYALTY_READ"},
- "SearchLoyaltyRewards": []string{"LOYALTY_READ"},
- "DeleteLoyaltyReward": []string{"LOYALTY_WRITE"},
- "SearchLoyaltyEvents": []string{"LOYALTY_READ"},
- },
- },
- {
- "Merchants": {
- "ListMerchants": []string{"MERCHANT_PROFILE_READ"},
- "RetrieveMerchant": []string{"MERCHANT_PROFILE_READ"},
- },
- },
- {
- "Merchant Custom Attributes": {
- "CreateMerchantCustomAttributeDefinition": []string{"MERCHANT_PROFILE_WRITE"},
- "UpdateMerchantCustomAttributeDefinition": []string{"MERCHANT_PROFILE_WRITE"},
- "ListMerchantCustomAttributeDefinitions": []string{"MERCHANT_PROFILE_READ"},
- "RetrieveMerchantCustomAttributeDefinition": []string{"MERCHANT_PROFILE_READ"},
- "DeleteMerchantCustomAttributeDefinition": []string{"MERCHANT_PROFILE_WRITE"},
- "UpsertMerchantCustomAttribute": []string{"MERCHANT_PROFILE_WRITE"},
- "BulkUpsertMerchantCustomAttributes": []string{"MERCHANT_PROFILE_WRITE"},
- "ListMerchantCustomAttributes": []string{"MERCHANT_PROFILE_READ"},
- "RetrieveMerchantCustomAttribute": []string{"MERCHANT_PROFILE_READ"},
- "DeleteMerchantCustomAttribute": []string{"MERCHANT_PROFILE_WRITE"},
- "BulkDeleteMerchantCustomAttributes": []string{"MERCHANT_PROFILE_WRITE"},
- },
- },
- {
- "Mobile Authorization": {
- "CreateMobileAuthorizationCode": []string{"PAYMENTS_WRITE_IN_PERSON"},
- },
- },
- {
- "Orders": {
- "CloneOrder": []string{"ORDERS_WRITE"},
- "CreateOrder": []string{"ORDERS_WRITE"},
- "BatchRetrieveOrders": []string{"ORDERS_READ"},
- "PayOrder": []string{"ORDERS_WRITE", "PAYMENTS_WRITE"},
- "RetrieveOrder": []string{"ORDERS_WRITE", "ORDERS_READ"},
- "SearchOrders": []string{"ORDERS_READ"},
- "UpdateOrder": []string{"ORDERS_WRITE"},
- },
- },
- {
- "Order Custom Attributes": {
- "CreateOrderCustomAttributeDefinition": []string{"ORDERS_WRITE"},
- "UpdateOrderCustomAttributeDefinition": []string{"ORDERS_WRITE"},
- "ListOrderCustomAttributeDefinitions": []string{"ORDERS_READ"},
- "RetrieveOrderCustomAttributeDefinition": []string{"ORDERS_READ"},
- "DeleteOrderCustomAttributeDefinition": []string{"ORDERS_WRITE"},
- "UpsertOrderCustomAttribute": []string{"ORDERS_WRITE"},
- "BulkUpsertOrderCustomAttributes": []string{"ORDERS_WRITE"},
- "ListOrderCustomAttributes": []string{"ORDERS_READ"},
- "RetrieveOrderCustomAttribute": []string{"ORDERS_READ"},
- "DeleteOrderCustomAttribute": []string{"ORDERS_WRITE"},
- "BulkDeleteOrderCustomAttributes": []string{"ORDERS_WRITE"},
- },
- },
- {
- "Payments and Refunds": {
- "CancelPayment": []string{"PAYMENTS_WRITE"},
- "CancelPaymentByIdempotencyKey": []string{"PAYMENTS_WRITE"},
- "CompletePayment": []string{"PAYMENTS_WRITE"},
- "CreatePayment": []string{"PAYMENTS_WRITE", "PAYMENTS_WRITE_SHARED_ONFILE", "PAYMENTS_WRITE_ADDITIONAL_RECIPIENTS"},
- "GetPayment": []string{"PAYMENTS_READ"},
- "GetPaymentRefund": []string{"PAYMENTS_READ"},
- "ListPayments": []string{"PAYMENTS_READ"},
- "ListPaymentRefunds": []string{"PAYMENTS_READ"},
- "RefundPayment": []string{"PAYMENTS_WRITE", "PAYMENTS_WRITE_ADDITIONAL_RECIPIENTS"},
- },
- },
- {
- "Payouts": {
- "ListPayouts": []string{"PAYOUTS_READ"},
- "GetPayout": []string{"PAYOUTS_READ"},
- "ListPayoutEntries": []string{"PAYOUTS_READ"},
- },
- },
- {
- "Sites": {
- "ListSites": []string{"ONLINE_STORE_SITE_READ"},
- },
- },
- {
- "Snippets": {
- "UpsertSnippet": []string{"ONLINE_STORE_SNIPPETS_WRITE"},
- "RetrieveSnippet": []string{"ONLINE_STORE_SNIPPETS_READ"},
- "DeleteSnippet": []string{"ONLINE_STORE_SNIPPETS_WRITE"},
- },
- },
- {
- "Subscriptions": {
- "CreateSubscription": []string{"CUSTOMERS_READ", "PAYMENTS_WRITE", "SUBSCRIPTIONS_WRITE", "ITEMS_READ", "ORDERS_WRITE", "INVOICES_WRITE"},
- "SearchSubscriptions": []string{"SUBSCRIPTIONS_READ"},
- "RetrieveSubscription": []string{"SUBSCRIPTIONS_READ"},
- "UpdateSubscription": []string{"CUSTOMERS_READ", "PAYMENTS_WRITE", "SUBSCRIPTIONS_WRITE", "ITEMS_READ", "ORDERS_WRITE", "INVOICES_WRITE"},
- "CancelSubscription": []string{"SUBSCRIPTIONS_WRITE"},
- "ListSubscriptionEvents": []string{"SUBSCRIPTIONS_READ"},
- "ResumeSubscription": []string{"CUSTOMERS_READ", "PAYMENTS_WRITE", "SUBSCRIPTIONS_WRITE", "ITEMS_READ", "ORDERS_WRITE", "INVOICES_WRITE"},
- "PauseSubscription": []string{"CUSTOMERS_READ", "PAYMENTS_WRITE", "SUBSCRIPTIONS_WRITE", "ITEMS_READ", "ORDERS_WRITE", "INVOICES_WRITE"},
- "SwapPlan": []string{"CUSTOMERS_READ", "PAYMENTS_WRITE", "SUBSCRIPTIONS_WRITE", "ITEMS_READ", "ORDERS_WRITE", "INVOICES_WRITE"},
- "DeleteSubscriptionAction": []string{"SUBSCRIPTIONS_WRITE"},
- },
- },
- {
- "Team": {
- "BulkCreateTeamMembers": []string{"EMPLOYEES_WRITE"},
- "BulkUpdateTeamMembers": []string{"EMPLOYEES_WRITE"},
- "CreateTeamMember": []string{"EMPLOYEES_WRITE"},
- "UpdateTeamMember": []string{"EMPLOYEES_WRITE"},
- "RetrieveTeamMember": []string{"EMPLOYEES_READ"},
- "RetrieveWageSetting": []string{"EMPLOYEES_READ"},
- "SearchTeamMembers": []string{"EMPLOYEES_READ"},
- "UpdateWageSetting": []string{"EMPLOYEES_WRITE"},
- },
- },
- {
- "Terminal": {
- "CreateTerminalCheckout": []string{"PAYMENTS_WRITE"},
- "CancelTerminalCheckout": []string{"PAYMENTS_WRITE"},
- "GetTerminalCheckout": []string{"PAYMENTS_READ"},
- "SearchTerminalCheckouts": []string{"PAYMENTS_READ"},
- "CreateTerminalRefund": []string{"PAYMENTS_WRITE"},
- "CancelTerminalRefund": []string{"PAYMENTS_WRITE"},
- "GetTerminalRefund": []string{"PAYMENTS_READ"},
- "SearchTerminalRefunds": []string{"PAYMENTS_READ"},
- "CreateTerminalAction": []string{"PAYMENTS_WRITE"},
- "CancelTerminalAction": []string{"PAYMENTS_WRITE"},
- "GetTerminalAction": []string{"PAYMENTS_READ", "CUSTOMERS_READ"},
- "SearchTerminalAction": []string{"PAYMENTS_READ"},
- },
- },
- {
- "Vendors": {
- "BulkCreateVendors": []string{"VENDOR_WRITE"},
- "BulkRetrieveVendors": []string{"VENDOR_READ"},
- "BulkUpdateVendors": []string{"VENDOR_WRITE"},
- "CreateVendor": []string{"VENDOR_WRITE"},
- "SearchVendors": []string{"VENDOR_READ"},
- "RetrieveVendor": []string{"VENDOR_READ"},
- "UpdateVendors": []string{"VENDOR_WRITE"},
- },
- },
-}
diff --git a/pkg/analyzer/analyzers/square/square.go b/pkg/analyzer/analyzers/square/square.go
deleted file mode 100644
index 7a51c5d1141e..000000000000
--- a/pkg/analyzer/analyzers/square/square.go
+++ /dev/null
@@ -1,332 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go square
-
-package square
-
-import (
- "encoding/json"
- "errors"
- "net/http"
- "os"
- "strconv"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeSquare }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("key not found in credentialInfo")
- }
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeSquare,
- UnboundedResources: []analyzers.Resource{},
- Metadata: map[string]any{
- "expires_at": info.Permissions.ExpiresAt,
- "client_id": info.Permissions.ClientID,
- "merchant_id": info.Permissions.MerchantID,
- },
- }
-
- bindings, unboundedResources := getBindingsAndUnboundedResources(info.Permissions.Scopes)
-
- result.Bindings = bindings
- result.UnboundedResources = append(result.UnboundedResources, unboundedResources...)
- result.UnboundedResources = append(result.UnboundedResources, getTeamMembersResources(info.Team)...)
-
- return &result
-}
-
-// Convert given list of team members into resources
-func getTeamMembersResources(team TeamJSON) []analyzers.Resource {
- teamMembersResources := make([]analyzers.Resource, len(team.TeamMembers))
-
- for idx, teamMember := range team.TeamMembers {
- teamMembersResources[idx] = analyzers.Resource{
- Name: teamMember.FirstName + " " + teamMember.LastName,
- FullyQualifiedName: teamMember.Email,
- Type: "team_member",
- Metadata: map[string]any{
- "is_owner": teamMember.IsOwner,
- "created_at": teamMember.CreatedAt,
- },
- }
- }
-
- return teamMembersResources
-}
-
-// Build a list of Bindings and UnboundedResources by referencing the category permissions list and
-// checking with the given scopes
-func getBindingsAndUnboundedResources(scopes []string) ([]analyzers.Binding, []analyzers.Resource) {
- bindings := []analyzers.Binding{}
- unboundedResources := []analyzers.Resource{}
- for _, permissions_category := range permissions_slice {
- for category, permissions := range permissions_category {
- parentResource := analyzers.Resource{
- Name: category,
- FullyQualifiedName: category,
- Type: "category",
- Metadata: nil,
- Parent: nil,
- }
- categoryBinding := make([]analyzers.Binding, 0)
- for endpoint, requiredPermissions := range permissions {
- resource := analyzers.Resource{
- Name: endpoint,
- FullyQualifiedName: endpoint,
- Type: "endpoint",
- Metadata: nil,
- Parent: &parentResource,
- }
- for _, permission := range requiredPermissions {
- if _, ok := StringToPermission[permission]; !ok { // skip unknown permissions
- continue
- }
- if contains(scopes, permission) {
- categoryBinding = append(categoryBinding, analyzers.Binding{
- Resource: resource,
- Permission: analyzers.Permission{
- Value: permission,
- },
- })
- }
- }
- }
- if len(categoryBinding) == 0 {
- unboundedResources = append(unboundedResources, parentResource)
- } else {
- bindings = append(bindings, categoryBinding...)
- }
- }
- }
-
- return bindings, unboundedResources
-}
-
-type TeamJSON struct {
- TeamMembers []struct {
- IsOwner bool `json:"is_owner"`
- FirstName string `json:"given_name"`
- LastName string `json:"family_name"`
- Email string `json:"email_address"`
- CreatedAt string `json:"created_at"`
- } `json:"team_members"`
-}
-
-type PermissionsJSON struct {
- Scopes []string `json:"scopes"`
- ExpiresAt string `json:"expires_at"`
- ClientID string `json:"client_id"`
- MerchantID string `json:"merchant_id"`
-}
-
-type SecretInfo struct {
- Permissions PermissionsJSON
- Team TeamJSON
-}
-
-func getPermissions(cfg *config.Config, key string) (PermissionsJSON, error) {
- var permissions PermissionsJSON
-
- // POST request is considered as non-safe. Square Post request does not change any state.
- // We are using unrestricted client to avoid error for non-safe API request.
- client := analyzers.NewAnalyzeClientUnrestricted(cfg)
- req, err := http.NewRequest("POST", "https://connect.squareup.com/oauth2/token/status", nil)
- if err != nil {
- return permissions, err
- }
-
- req.Header.Add("Authorization", "Bearer "+key)
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Square-Version", "2024-06-04")
-
- resp, err := client.Do(req)
- if err != nil {
- return permissions, err
- }
-
- if resp.StatusCode != 200 {
- return permissions, nil
- }
-
- defer resp.Body.Close()
-
- err = json.NewDecoder(resp.Body).Decode(&permissions)
- if err != nil {
- return permissions, err
- }
- return permissions, nil
-}
-
-func getUsers(cfg *config.Config, key string) (TeamJSON, error) {
- var team TeamJSON
-
- // POST request is considered as non-safe. Square Post request does not change any state.
- // We are using unrestricted client to avoid error for non-safe API request.
- client := analyzers.NewAnalyzeClientUnrestricted(cfg)
- req, err := http.NewRequest("POST", "https://connect.squareup.com/v2/team-members/search", nil)
- if err != nil {
- return team, err
- }
-
- req.Header.Add("Authorization", "Bearer "+key)
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Square-Version", "2024-06-04")
-
- q := req.URL.Query()
- q.Add("limit", "200")
- req.URL.RawQuery = q.Encode()
-
- resp, err := client.Do(req)
- if err != nil {
- return team, err
- }
-
- if resp.StatusCode != 200 {
- return team, nil
- }
-
- defer resp.Body.Close()
-
- err = json.NewDecoder(resp.Body).Decode(&team)
- if err != nil {
- return team, err
- }
- return team, nil
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- permissions, err := getPermissions(cfg, key)
- if err != nil {
- return nil, err
- }
-
- team, err := getUsers(cfg, key)
- if err != nil {
- return nil, err
- }
-
- return &SecretInfo{
- Permissions: permissions,
- Team: team,
- }, nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- // ToDo: Add in logging
- if cfg.LoggingEnabled {
- color.Red("[x] Logging is not supported for this analyzer.")
- return
- }
-
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
-
- if info.Permissions.MerchantID == "" {
- color.Red("[x] Invalid Square API Key")
- return
- }
- color.Green("[!] Valid Square API Key\n\n")
- color.Yellow("Merchant ID: %s", info.Permissions.MerchantID)
- color.Yellow("Client ID: %s", info.Permissions.ClientID)
- if info.Permissions.ExpiresAt == "" {
- color.Green("Expires: Never\n\n")
- } else {
- color.Yellow("Expires: %s\n\n", info.Permissions.ExpiresAt)
- }
- printPermissions(info.Permissions.Scopes, cfg.ShowAll)
-
- printTeamMembers(info.Team)
-}
-
-func contains(s []string, e string) bool {
- for _, a := range s {
- if a == e {
- return true
- }
- }
- return false
-}
-
-func printPermissions(scopes []string, showAll bool) {
- isAccessToken := true
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"API Category", "Accessible Endpoints"})
- for _, permissions_slice := range permissions_slice {
- for category, permissions := range permissions_slice {
- accessibleEndpoints := []string{}
- for endpoint, requiredPermissions := range permissions {
- hasAllPermissions := true
- for _, permission := range requiredPermissions {
- if !contains(scopes, permission) {
- hasAllPermissions = false
- isAccessToken = false
- break
- }
- }
- if hasAllPermissions {
- accessibleEndpoints = append(accessibleEndpoints, endpoint)
- }
- }
- if len(accessibleEndpoints) == 0 {
- t.AppendRow([]interface{}{category, ""})
- } else {
- t.AppendRow([]interface{}{color.GreenString(category), color.GreenString(strings.Join(accessibleEndpoints, ", "))})
- }
- }
- }
- if isAccessToken {
- color.Green("[i] Permissions: Full Access")
- } else {
- color.Yellow("[i] Permissions:")
- }
- if !isAccessToken || showAll {
- t.SetColumnConfigs([]table.ColumnConfig{
- {Number: 2, WidthMax: 100},
- })
- t.Render()
- }
-}
-
-func printTeamMembers(team TeamJSON) {
- if len(team.TeamMembers) == 0 {
- color.Red("\n[x] No team members found")
- return
- }
- color.Yellow("\n[i] Team Members (don't imply any permissions)")
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"First Name", "Last Name", "Email", "Owner", "Created At"})
- for _, member := range team.TeamMembers {
- t.AppendRow([]interface{}{color.GreenString(member.FirstName), color.GreenString(member.LastName), color.GreenString(member.Email), color.GreenString(strconv.FormatBool(member.IsOwner)), color.GreenString(member.CreatedAt)})
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/square/square_test.go b/pkg/analyzer/analyzers/square/square_test.go
deleted file mode 100644
index c24f7f0d6cf0..000000000000
--- a/pkg/analyzer/analyzers/square/square_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package square
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid Square key",
- key: testSecrets.MustGetField("SQUARE_SECRET"),
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal(tt.want, &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // // Bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/stripe/expected_output.json b/pkg/analyzer/analyzers/stripe/expected_output.json
deleted file mode 100644
index 0a4dc69e0718..000000000000
--- a/pkg/analyzer/analyzers/stripe/expected_output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"AnalyzerType":19,"Bindings":[{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Customers:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Customer session:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Ephemeral keys:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Payment Method Domains:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"SetupIntents:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Sources:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Balance:Read","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Test clocks:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Files:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Funding Instructions:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"PaymentIntents:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"PaymentMethods:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Shipping Rates:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Tokens:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Charges:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Confirmation token:Read","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Confirmation token (client):Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Apple Pay Domains:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Disputes:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Events:Read","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Payouts:Write","Parent":null}},{"Resource":{"Name":"Core","FullyQualifiedName":"Core","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Products:Write","Parent":null}},{"Resource":{"Name":"Checkout","FullyQualifiedName":"Checkout","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Checkout Sessions:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Tax Rates:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Meter Events:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Coupons:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Promotion Codes:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Credit notes:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Subscriptions:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Quote:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Tax IDs:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Usage Records:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Meters:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Meter Event Adjustments:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Customer portal:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Invoices:","Parent":null}},{"Resource":{"Name":"Billing","FullyQualifiedName":"Billing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Prices:","Parent":null}},{"Resource":{"Name":"Connect","FullyQualifiedName":"Connect","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Transfers:Read","Parent":null}},{"Resource":{"Name":"Connect","FullyQualifiedName":"Connect","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Application Fees:Read","Parent":null}},{"Resource":{"Name":"Connect","FullyQualifiedName":"Connect","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Login Links:","Parent":null}},{"Resource":{"Name":"Connect","FullyQualifiedName":"Connect","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Account Links:","Parent":null}},{"Resource":{"Name":"Connect","FullyQualifiedName":"Connect","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Top-ups:Write","Parent":null}},{"Resource":{"Name":"Orders","FullyQualifiedName":"Orders","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Orders:","Parent":null}},{"Resource":{"Name":"Orders","FullyQualifiedName":"Orders","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"SKUs:","Parent":null}},{"Resource":{"Name":"Issuing","FullyQualifiedName":"Issuing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Tokens:","Parent":null}},{"Resource":{"Name":"Issuing","FullyQualifiedName":"Issuing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Transactions:","Parent":null}},{"Resource":{"Name":"Issuing","FullyQualifiedName":"Issuing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Authorizations:","Parent":null}},{"Resource":{"Name":"Issuing","FullyQualifiedName":"Issuing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Cardholders:","Parent":null}},{"Resource":{"Name":"Issuing","FullyQualifiedName":"Issuing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Cards:","Parent":null}},{"Resource":{"Name":"Issuing","FullyQualifiedName":"Issuing","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Disputes:","Parent":null}},{"Resource":{"Name":"Reporting","FullyQualifiedName":"Reporting","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Report Runs and Report Types:","Parent":null}},{"Resource":{"Name":"Identity","FullyQualifiedName":"Identity","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Verification Sessions and Reports:","Parent":null}},{"Resource":{"Name":"Webhook","FullyQualifiedName":"Webhook","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Webhook Endpoints:","Parent":null}},{"Resource":{"Name":"Payment Links","FullyQualifiedName":"Payment Links","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Payment Links:","Parent":null}},{"Resource":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Configurations:","Parent":null}},{"Resource":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Locations:","Parent":null}},{"Resource":{"Name":"Terminal","FullyQualifiedName":"Terminal","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Readers:","Parent":null}},{"Resource":{"Name":"Tax","FullyQualifiedName":"Tax","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Tax Calculations and Transactions:","Parent":null}},{"Resource":{"Name":"Tax","FullyQualifiedName":"Tax","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Tax Settings and Registrations:","Parent":null}},{"Resource":{"Name":"Radar","FullyQualifiedName":"Radar","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Reviews:","Parent":null}},{"Resource":{"Name":"Climate","FullyQualifiedName":"Climate","Type":"category","Metadata":null,"Parent":null},"Permission":{"Value":"Climate Orders:","Parent":null}}],"UnboundedResources":[{"Name":"Stripe CLI","FullyQualifiedName":"Stripe CLI","Type":"category","Metadata":null,"Parent":null}],"Metadata":{"key_env":"Test","key_type":"Restricted"}}
\ No newline at end of file
diff --git a/pkg/analyzer/analyzers/stripe/permissions.go b/pkg/analyzer/analyzers/stripe/permissions.go
deleted file mode 100644
index b34ec14e1ea3..000000000000
--- a/pkg/analyzer/analyzers/stripe/permissions.go
+++ /dev/null
@@ -1,561 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package stripe
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- ConnectedAccountRead Permission = iota
- AccountLinkWrite Permission = iota
- ApplePayDomainRead Permission = iota
- ApplePayDomainWrite Permission = iota
- ApplicationFeeRead Permission = iota
- ApplicationFeeWrite Permission = iota
- BalanceRead Permission = iota
- BalanceTransactionSourceRead Permission = iota
- BillingClockRead Permission = iota
- BillingClockWrite Permission = iota
- ChargeRead Permission = iota
- ChargeWrite Permission = iota
- CheckoutSessionRead Permission = iota
- CheckoutSessionWrite Permission = iota
- TerminalConfigurationRead Permission = iota
- TerminalConfigurationWrite Permission = iota
- TerminalConnectionTokenWrite Permission = iota
- CouponRead Permission = iota
- CouponWrite Permission = iota
- CreditNoteRead Permission = iota
- CreditNoteWrite Permission = iota
- CustomerPortalRead Permission = iota
- CustomerPortalWrite Permission = iota
- CustomerRead Permission = iota
- CustomerWrite Permission = iota
- DisputeRead Permission = iota
- DisputeWrite Permission = iota
- EditLinkWrite Permission = iota
- ElementsWrite Permission = iota
- EventRead Permission = iota
- FileRead Permission = iota
- FileWrite Permission = iota
- InvoiceRead Permission = iota
- InvoiceWrite Permission = iota
- IssuingAuthorizationRead Permission = iota
- IssuingAuthorizationWrite Permission = iota
- IssuingCardRead Permission = iota
- IssuingCardWrite Permission = iota
- IssuingCardholderRead Permission = iota
- IssuingCardholderWrite Permission = iota
- IssuingDisputeRead Permission = iota
- IssuingDisputeWrite Permission = iota
- IssuingTransactionRead Permission = iota
- IssuingTransactionWrite Permission = iota
- TerminalLocationRead Permission = iota
- TerminalLocationWrite Permission = iota
- MandateRead Permission = iota
- MandateWrite Permission = iota
- OrderRead Permission = iota
- OrderWrite Permission = iota
- PaymentIntentRead Permission = iota
- PaymentIntentWrite Permission = iota
- PaymentLinksRead Permission = iota
- PaymentLinksWrite Permission = iota
- PaymentMethodRead Permission = iota
- PaymentMethodWrite Permission = iota
- PayoutRead Permission = iota
- PayoutWrite Permission = iota
- PlanRead Permission = iota
- PlanWrite Permission = iota
- ProductRead Permission = iota
- ProductWrite Permission = iota
- PromotionCodeRead Permission = iota
- PromotionCodeWrite Permission = iota
- QuoteRead Permission = iota
- QuoteWrite Permission = iota
- TerminalReaderRead Permission = iota
- TerminalReaderWrite Permission = iota
- ReportRunsAndReportTypesRead Permission = iota
- ReviewRead Permission = iota
- ReviewWrite Permission = iota
- SecretWrite Permission = iota
- SetupIntentRead Permission = iota
- SetupIntentWrite Permission = iota
- ShippingRateRead Permission = iota
- ShippingRateWrite Permission = iota
- SkuRead Permission = iota
- SkuWrite Permission = iota
- SourceRead Permission = iota
- SourceWrite Permission = iota
- SubscriptionRead Permission = iota
- SubscriptionWrite Permission = iota
- TaxRateRead Permission = iota
- TaxRateWrite Permission = iota
- TaxSettingsRead Permission = iota
- TaxSettingsWrite Permission = iota
- TaxCalculationsAndTransactionsRead Permission = iota
- TaxCalculationsAndTransactionsWrite Permission = iota
- TokenRead Permission = iota
- TokenWrite Permission = iota
- TopUpRead Permission = iota
- TopUpWrite Permission = iota
- TransferRead Permission = iota
- TransferWrite Permission = iota
- UsageRecordRead Permission = iota
- UsageRecordWrite Permission = iota
- UserEmailRead Permission = iota
- WebhookRead Permission = iota
- WebhookWrite Permission = iota
- IssuingCardSensitiveRead Permission = iota
- FundingInstructionRead Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- ConnectedAccountRead: "connected_account_read",
- AccountLinkWrite: "account_link_write",
- ApplePayDomainRead: "apple_pay_domain_read",
- ApplePayDomainWrite: "apple_pay_domain_write",
- ApplicationFeeRead: "application_fee_read",
- ApplicationFeeWrite: "application_fee_write",
- BalanceRead: "balance_read",
- BalanceTransactionSourceRead: "balance_transaction_source_read",
- BillingClockRead: "billing_clock_read",
- BillingClockWrite: "billing_clock_write",
- ChargeRead: "charge_read",
- ChargeWrite: "charge_write",
- CheckoutSessionRead: "checkout_session_read",
- CheckoutSessionWrite: "checkout_session_write",
- TerminalConfigurationRead: "terminal_configuration_read",
- TerminalConfigurationWrite: "terminal_configuration_write",
- TerminalConnectionTokenWrite: "terminal_connection_token_write",
- CouponRead: "coupon_read",
- CouponWrite: "coupon_write",
- CreditNoteRead: "credit_note_read",
- CreditNoteWrite: "credit_note_write",
- CustomerPortalRead: "customer_portal_read",
- CustomerPortalWrite: "customer_portal_write",
- CustomerRead: "customer_read",
- CustomerWrite: "customer_write",
- DisputeRead: "dispute_read",
- DisputeWrite: "dispute_write",
- EditLinkWrite: "edit_link_write",
- ElementsWrite: "elements_write",
- EventRead: "event_read",
- FileRead: "file_read",
- FileWrite: "file_write",
- InvoiceRead: "invoice_read",
- InvoiceWrite: "invoice_write",
- IssuingAuthorizationRead: "issuing_authorization_read",
- IssuingAuthorizationWrite: "issuing_authorization_write",
- IssuingCardRead: "issuing_card_read",
- IssuingCardWrite: "issuing_card_write",
- IssuingCardholderRead: "issuing_cardholder_read",
- IssuingCardholderWrite: "issuing_cardholder_write",
- IssuingDisputeRead: "issuing_dispute_read",
- IssuingDisputeWrite: "issuing_dispute_write",
- IssuingTransactionRead: "issuing_transaction_read",
- IssuingTransactionWrite: "issuing_transaction_write",
- TerminalLocationRead: "terminal_location_read",
- TerminalLocationWrite: "terminal_location_write",
- MandateRead: "mandate_read",
- MandateWrite: "mandate_write",
- OrderRead: "order_read",
- OrderWrite: "order_write",
- PaymentIntentRead: "payment_intent_read",
- PaymentIntentWrite: "payment_intent_write",
- PaymentLinksRead: "payment_links_read",
- PaymentLinksWrite: "payment_links_write",
- PaymentMethodRead: "payment_method_read",
- PaymentMethodWrite: "payment_method_write",
- PayoutRead: "payout_read",
- PayoutWrite: "payout_write",
- PlanRead: "plan_read",
- PlanWrite: "plan_write",
- ProductRead: "product_read",
- ProductWrite: "product_write",
- PromotionCodeRead: "promotion_code_read",
- PromotionCodeWrite: "promotion_code_write",
- QuoteRead: "quote_read",
- QuoteWrite: "quote_write",
- TerminalReaderRead: "terminal_reader_read",
- TerminalReaderWrite: "terminal_reader_write",
- ReportRunsAndReportTypesRead: "report_runs_and_report_types_read",
- ReviewRead: "review_read",
- ReviewWrite: "review_write",
- SecretWrite: "secret_write",
- SetupIntentRead: "setup_intent_read",
- SetupIntentWrite: "setup_intent_write",
- ShippingRateRead: "shipping_rate_read",
- ShippingRateWrite: "shipping_rate_write",
- SkuRead: "sku_read",
- SkuWrite: "sku_write",
- SourceRead: "source_read",
- SourceWrite: "source_write",
- SubscriptionRead: "subscription_read",
- SubscriptionWrite: "subscription_write",
- TaxRateRead: "tax_rate_read",
- TaxRateWrite: "tax_rate_write",
- TaxSettingsRead: "tax_settings_read",
- TaxSettingsWrite: "tax_settings_write",
- TaxCalculationsAndTransactionsRead: "tax_calculations_and_transactions_read",
- TaxCalculationsAndTransactionsWrite: "tax_calculations_and_transactions_write",
- TokenRead: "token_read",
- TokenWrite: "token_write",
- TopUpRead: "top_up_read",
- TopUpWrite: "top_up_write",
- TransferRead: "transfer_read",
- TransferWrite: "transfer_write",
- UsageRecordRead: "usage_record_read",
- UsageRecordWrite: "usage_record_write",
- UserEmailRead: "user_email_read",
- WebhookRead: "webhook_read",
- WebhookWrite: "webhook_write",
- IssuingCardSensitiveRead: "issuing_card_sensitive_read",
- FundingInstructionRead: "funding_instruction_read",
- }
-
- StringToPermission = map[string]Permission{
- "connected_account_read": ConnectedAccountRead,
- "account_link_write": AccountLinkWrite,
- "apple_pay_domain_read": ApplePayDomainRead,
- "apple_pay_domain_write": ApplePayDomainWrite,
- "application_fee_read": ApplicationFeeRead,
- "application_fee_write": ApplicationFeeWrite,
- "balance_read": BalanceRead,
- "balance_transaction_source_read": BalanceTransactionSourceRead,
- "billing_clock_read": BillingClockRead,
- "billing_clock_write": BillingClockWrite,
- "charge_read": ChargeRead,
- "charge_write": ChargeWrite,
- "checkout_session_read": CheckoutSessionRead,
- "checkout_session_write": CheckoutSessionWrite,
- "terminal_configuration_read": TerminalConfigurationRead,
- "terminal_configuration_write": TerminalConfigurationWrite,
- "terminal_connection_token_write": TerminalConnectionTokenWrite,
- "coupon_read": CouponRead,
- "coupon_write": CouponWrite,
- "credit_note_read": CreditNoteRead,
- "credit_note_write": CreditNoteWrite,
- "customer_portal_read": CustomerPortalRead,
- "customer_portal_write": CustomerPortalWrite,
- "customer_read": CustomerRead,
- "customer_write": CustomerWrite,
- "dispute_read": DisputeRead,
- "dispute_write": DisputeWrite,
- "edit_link_write": EditLinkWrite,
- "elements_write": ElementsWrite,
- "event_read": EventRead,
- "file_read": FileRead,
- "file_write": FileWrite,
- "invoice_read": InvoiceRead,
- "invoice_write": InvoiceWrite,
- "issuing_authorization_read": IssuingAuthorizationRead,
- "issuing_authorization_write": IssuingAuthorizationWrite,
- "issuing_card_read": IssuingCardRead,
- "issuing_card_write": IssuingCardWrite,
- "issuing_cardholder_read": IssuingCardholderRead,
- "issuing_cardholder_write": IssuingCardholderWrite,
- "issuing_dispute_read": IssuingDisputeRead,
- "issuing_dispute_write": IssuingDisputeWrite,
- "issuing_transaction_read": IssuingTransactionRead,
- "issuing_transaction_write": IssuingTransactionWrite,
- "terminal_location_read": TerminalLocationRead,
- "terminal_location_write": TerminalLocationWrite,
- "mandate_read": MandateRead,
- "mandate_write": MandateWrite,
- "order_read": OrderRead,
- "order_write": OrderWrite,
- "payment_intent_read": PaymentIntentRead,
- "payment_intent_write": PaymentIntentWrite,
- "payment_links_read": PaymentLinksRead,
- "payment_links_write": PaymentLinksWrite,
- "payment_method_read": PaymentMethodRead,
- "payment_method_write": PaymentMethodWrite,
- "payout_read": PayoutRead,
- "payout_write": PayoutWrite,
- "plan_read": PlanRead,
- "plan_write": PlanWrite,
- "product_read": ProductRead,
- "product_write": ProductWrite,
- "promotion_code_read": PromotionCodeRead,
- "promotion_code_write": PromotionCodeWrite,
- "quote_read": QuoteRead,
- "quote_write": QuoteWrite,
- "terminal_reader_read": TerminalReaderRead,
- "terminal_reader_write": TerminalReaderWrite,
- "report_runs_and_report_types_read": ReportRunsAndReportTypesRead,
- "review_read": ReviewRead,
- "review_write": ReviewWrite,
- "secret_write": SecretWrite,
- "setup_intent_read": SetupIntentRead,
- "setup_intent_write": SetupIntentWrite,
- "shipping_rate_read": ShippingRateRead,
- "shipping_rate_write": ShippingRateWrite,
- "sku_read": SkuRead,
- "sku_write": SkuWrite,
- "source_read": SourceRead,
- "source_write": SourceWrite,
- "subscription_read": SubscriptionRead,
- "subscription_write": SubscriptionWrite,
- "tax_rate_read": TaxRateRead,
- "tax_rate_write": TaxRateWrite,
- "tax_settings_read": TaxSettingsRead,
- "tax_settings_write": TaxSettingsWrite,
- "tax_calculations_and_transactions_read": TaxCalculationsAndTransactionsRead,
- "tax_calculations_and_transactions_write": TaxCalculationsAndTransactionsWrite,
- "token_read": TokenRead,
- "token_write": TokenWrite,
- "top_up_read": TopUpRead,
- "top_up_write": TopUpWrite,
- "transfer_read": TransferRead,
- "transfer_write": TransferWrite,
- "usage_record_read": UsageRecordRead,
- "usage_record_write": UsageRecordWrite,
- "user_email_read": UserEmailRead,
- "webhook_read": WebhookRead,
- "webhook_write": WebhookWrite,
- "issuing_card_sensitive_read": IssuingCardSensitiveRead,
- "funding_instruction_read": FundingInstructionRead,
- }
-
- PermissionIDs = map[Permission]int{
- ConnectedAccountRead: 1,
- AccountLinkWrite: 2,
- ApplePayDomainRead: 3,
- ApplePayDomainWrite: 4,
- ApplicationFeeRead: 5,
- ApplicationFeeWrite: 6,
- BalanceRead: 7,
- BalanceTransactionSourceRead: 8,
- BillingClockRead: 9,
- BillingClockWrite: 10,
- ChargeRead: 11,
- ChargeWrite: 12,
- CheckoutSessionRead: 13,
- CheckoutSessionWrite: 14,
- TerminalConfigurationRead: 15,
- TerminalConfigurationWrite: 16,
- TerminalConnectionTokenWrite: 17,
- CouponRead: 18,
- CouponWrite: 19,
- CreditNoteRead: 20,
- CreditNoteWrite: 21,
- CustomerPortalRead: 22,
- CustomerPortalWrite: 23,
- CustomerRead: 24,
- CustomerWrite: 25,
- DisputeRead: 26,
- DisputeWrite: 27,
- EditLinkWrite: 28,
- ElementsWrite: 29,
- EventRead: 30,
- FileRead: 31,
- FileWrite: 32,
- InvoiceRead: 33,
- InvoiceWrite: 34,
- IssuingAuthorizationRead: 35,
- IssuingAuthorizationWrite: 36,
- IssuingCardRead: 37,
- IssuingCardWrite: 38,
- IssuingCardholderRead: 39,
- IssuingCardholderWrite: 40,
- IssuingDisputeRead: 41,
- IssuingDisputeWrite: 42,
- IssuingTransactionRead: 43,
- IssuingTransactionWrite: 44,
- TerminalLocationRead: 45,
- TerminalLocationWrite: 46,
- MandateRead: 47,
- MandateWrite: 48,
- OrderRead: 49,
- OrderWrite: 50,
- PaymentIntentRead: 51,
- PaymentIntentWrite: 52,
- PaymentLinksRead: 53,
- PaymentLinksWrite: 54,
- PaymentMethodRead: 55,
- PaymentMethodWrite: 56,
- PayoutRead: 57,
- PayoutWrite: 58,
- PlanRead: 59,
- PlanWrite: 60,
- ProductRead: 61,
- ProductWrite: 62,
- PromotionCodeRead: 63,
- PromotionCodeWrite: 64,
- QuoteRead: 65,
- QuoteWrite: 66,
- TerminalReaderRead: 67,
- TerminalReaderWrite: 68,
- ReportRunsAndReportTypesRead: 69,
- ReviewRead: 70,
- ReviewWrite: 71,
- SecretWrite: 72,
- SetupIntentRead: 73,
- SetupIntentWrite: 74,
- ShippingRateRead: 75,
- ShippingRateWrite: 76,
- SkuRead: 77,
- SkuWrite: 78,
- SourceRead: 79,
- SourceWrite: 80,
- SubscriptionRead: 81,
- SubscriptionWrite: 82,
- TaxRateRead: 83,
- TaxRateWrite: 84,
- TaxSettingsRead: 85,
- TaxSettingsWrite: 86,
- TaxCalculationsAndTransactionsRead: 87,
- TaxCalculationsAndTransactionsWrite: 88,
- TokenRead: 89,
- TokenWrite: 90,
- TopUpRead: 91,
- TopUpWrite: 92,
- TransferRead: 93,
- TransferWrite: 94,
- UsageRecordRead: 95,
- UsageRecordWrite: 96,
- UserEmailRead: 97,
- WebhookRead: 98,
- WebhookWrite: 99,
- IssuingCardSensitiveRead: 100,
- FundingInstructionRead: 101,
- }
-
- IdToPermission = map[int]Permission{
- 1: ConnectedAccountRead,
- 2: AccountLinkWrite,
- 3: ApplePayDomainRead,
- 4: ApplePayDomainWrite,
- 5: ApplicationFeeRead,
- 6: ApplicationFeeWrite,
- 7: BalanceRead,
- 8: BalanceTransactionSourceRead,
- 9: BillingClockRead,
- 10: BillingClockWrite,
- 11: ChargeRead,
- 12: ChargeWrite,
- 13: CheckoutSessionRead,
- 14: CheckoutSessionWrite,
- 15: TerminalConfigurationRead,
- 16: TerminalConfigurationWrite,
- 17: TerminalConnectionTokenWrite,
- 18: CouponRead,
- 19: CouponWrite,
- 20: CreditNoteRead,
- 21: CreditNoteWrite,
- 22: CustomerPortalRead,
- 23: CustomerPortalWrite,
- 24: CustomerRead,
- 25: CustomerWrite,
- 26: DisputeRead,
- 27: DisputeWrite,
- 28: EditLinkWrite,
- 29: ElementsWrite,
- 30: EventRead,
- 31: FileRead,
- 32: FileWrite,
- 33: InvoiceRead,
- 34: InvoiceWrite,
- 35: IssuingAuthorizationRead,
- 36: IssuingAuthorizationWrite,
- 37: IssuingCardRead,
- 38: IssuingCardWrite,
- 39: IssuingCardholderRead,
- 40: IssuingCardholderWrite,
- 41: IssuingDisputeRead,
- 42: IssuingDisputeWrite,
- 43: IssuingTransactionRead,
- 44: IssuingTransactionWrite,
- 45: TerminalLocationRead,
- 46: TerminalLocationWrite,
- 47: MandateRead,
- 48: MandateWrite,
- 49: OrderRead,
- 50: OrderWrite,
- 51: PaymentIntentRead,
- 52: PaymentIntentWrite,
- 53: PaymentLinksRead,
- 54: PaymentLinksWrite,
- 55: PaymentMethodRead,
- 56: PaymentMethodWrite,
- 57: PayoutRead,
- 58: PayoutWrite,
- 59: PlanRead,
- 60: PlanWrite,
- 61: ProductRead,
- 62: ProductWrite,
- 63: PromotionCodeRead,
- 64: PromotionCodeWrite,
- 65: QuoteRead,
- 66: QuoteWrite,
- 67: TerminalReaderRead,
- 68: TerminalReaderWrite,
- 69: ReportRunsAndReportTypesRead,
- 70: ReviewRead,
- 71: ReviewWrite,
- 72: SecretWrite,
- 73: SetupIntentRead,
- 74: SetupIntentWrite,
- 75: ShippingRateRead,
- 76: ShippingRateWrite,
- 77: SkuRead,
- 78: SkuWrite,
- 79: SourceRead,
- 80: SourceWrite,
- 81: SubscriptionRead,
- 82: SubscriptionWrite,
- 83: TaxRateRead,
- 84: TaxRateWrite,
- 85: TaxSettingsRead,
- 86: TaxSettingsWrite,
- 87: TaxCalculationsAndTransactionsRead,
- 88: TaxCalculationsAndTransactionsWrite,
- 89: TokenRead,
- 90: TokenWrite,
- 91: TopUpRead,
- 92: TopUpWrite,
- 93: TransferRead,
- 94: TransferWrite,
- 95: UsageRecordRead,
- 96: UsageRecordWrite,
- 97: UserEmailRead,
- 98: WebhookRead,
- 99: WebhookWrite,
- 100: IssuingCardSensitiveRead,
- 101: FundingInstructionRead,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/stripe/permissions.yaml b/pkg/analyzer/analyzers/stripe/permissions.yaml
deleted file mode 100644
index 6afe913accf0..000000000000
--- a/pkg/analyzer/analyzers/stripe/permissions.yaml
+++ /dev/null
@@ -1,102 +0,0 @@
-permissions:
- - connected_account_read
- - account_link_write
- - apple_pay_domain_read
- - apple_pay_domain_write
- - application_fee_read
- - application_fee_write
- - balance_read
- - balance_transaction_source_read
- - billing_clock_read
- - billing_clock_write
- - charge_read
- - charge_write
- - checkout_session_read
- - checkout_session_write
- - terminal_configuration_read
- - terminal_configuration_write
- - terminal_connection_token_write
- - coupon_read
- - coupon_write
- - credit_note_read
- - credit_note_write
- - customer_portal_read
- - customer_portal_write
- - customer_read
- - customer_write
- - dispute_read
- - dispute_write
- - edit_link_write
- - elements_write
- - event_read
- - file_read
- - file_write
- - invoice_read
- - invoice_write
- - issuing_authorization_read
- - issuing_authorization_write
- - issuing_card_read
- - issuing_card_write
- - issuing_cardholder_read
- - issuing_cardholder_write
- - issuing_dispute_read
- - issuing_dispute_write
- - issuing_transaction_read
- - issuing_transaction_write
- - terminal_location_read
- - terminal_location_write
- - mandate_read
- - mandate_write
- - order_read
- - order_write
- - payment_intent_read
- - payment_intent_write
- - payment_links_read
- - payment_links_write
- - payment_method_read
- - payment_method_write
- - payout_read
- - payout_write
- - plan_read
- - plan_write
- - product_read
- - product_write
- - promotion_code_read
- - promotion_code_write
- - quote_read
- - quote_write
- - terminal_reader_read
- - terminal_reader_write
- - report_runs_and_report_types_read
- - review_read
- - review_write
- - secret_write
- - setup_intent_read
- - setup_intent_write
- - shipping_rate_read
- - shipping_rate_write
- - sku_read
- - sku_write
- - source_read
- - source_write
- - subscription_read
- - subscription_write
- - tax_rate_read
- - tax_rate_write
- - tax_settings_read
- - tax_settings_write
- - tax_calculations_and_transactions_read
- - tax_calculations_and_transactions_write
- - token_read
- - token_write
- - top_up_read
- - top_up_write
- - transfer_read
- - transfer_write
- - usage_record_read
- - usage_record_write
- - user_email_read
- - webhook_read
- - webhook_write
- - issuing_card_sensitive_read
- - funding_instruction_read
diff --git a/pkg/analyzer/analyzers/stripe/restricted.yaml b/pkg/analyzer/analyzers/stripe/restricted.yaml
deleted file mode 100644
index 9608d1dcb268..000000000000
--- a/pkg/analyzer/analyzers/stripe/restricted.yaml
+++ /dev/null
@@ -1,1416 +0,0 @@
-categories:
- Core:
- Apple Pay Domains:
- Read:
- Scope: rak_apple_pay_domain_read
- Endpoint: https://api.stripe.com/v1/apple_pay/domains
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_apple_pay_domains/GetApplePayDomains
- Note: ''
- Write:
- Scope: rak_apple_pay_domain_write
- Endpoint: https://api.stripe.com/v1/apple_pay/domains
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: ''
- Note: ''
- Balance:
- Read:
- Scope: rak_balance_read
- Endpoint: https://api.stripe.com/v1/balance
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/balance
- Note: ''
- Balance transaction sources:
- Read:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: 'I think we just build this one based off of all the others? Note that
- this permission also implies the following permissions: Application Fees
- (Read), Balance (Read), Financing Transactions (Read), Payouts (Read), Transfers
- (Read), and Balance Transfers (Read)'
- Balance Transfer:
- Read:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: Not sure this exists anymore
- Write:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: Not sure this exists anymore
- Test clocks:
- Read:
- Scope: rak_billing_clock_read
- Endpoint: https://api.stripe.com/v1/test_helpers/test_clocks
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/test_clocks/list
- Note: ''
- Write:
- Scope: rak_billing_clock_write
- Endpoint: https://api.stripe.com/v1/test_helpers/test_clocks
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/test_clocks/create
- Note: ''
- Charges:
- Read:
- Scope: rak_charge_read
- Endpoint: https://api.stripe.com/v1/charges
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/charges/list
- Note: ''
- Write:
- Scope: rak_charge_write
- Endpoint: https://api.stripe.com/v1/charges
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/charges/update
- Note: ''
- Confirmation token:
- Read:
- Scope: rak_confirmation_token_read
- Endpoint: https://api.stripe.com/v1/confirmation_tokens/nowaythiscanexist
- Method: GET
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/confirmation_tokens/retrieve
- Note: ''
- Confirmation token (client):
- Read:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: Not sure this exists anymore
- Write:
- Scope: rak_confirmation_token_client_write
- Endpoint: https://api.stripe.com/v1/test_helpers/confirmation_tokens
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/confirmation_tokens/test_create
- Note: ''
- Customers:
- Read:
- Scope: rak_customer_read
- Endpoint: https://api.stripe.com/v1/customers
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/customers/list
- Note: ''
- Write:
- Scope: rak_customer_write
- Endpoint: https://api.stripe.com/v1/customers/nowaythiscanexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/customers/update
- Note: Couldn't use "Create Customer", b/c default with no payload creates
- a customer.
- Customer session:
- Read:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: Not sure this exists anymore
- Write:
- Scope: rak_customer_session_write
- Endpoint: https://api.stripe.com/v1/customer_sessions
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/customer_sessions/create
- Note: ''
- Disputes:
- Read:
- Scope: rak_dispute_read
- Endpoint: https://api.stripe.com/v1/disputes
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/disputes/list
- Note: ''
- Write:
- Scope: rak_dispute_write
- Endpoint: https://api.stripe.com/v1/disputes/nowaycanthisexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/disputes/update
- Note: ''
- Events:
- Read:
- Scope: rak_event_read
- Endpoint: https://api.stripe.com/v1/events
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/events/list
- Note: ''
- Ephemeral keys:
- Read:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: ''
- Write:
- Scope: rak_ephemeral_key_write
- Endpoint: https://api.stripe.com/v1/ephemeral_keys
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_ephemeral_keys_key_
- Note: ''
- Files:
- Read:
- Scope: rak_file_read
- Endpoint: https://api.stripe.com/v1/files
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: ''
- Note: ''
- Write:
- Scope: ''
- Endpoint: https://files.stripe.com/v1/files
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: ''
- Note: On 403, it mistakenly says "rak_dispute_write" missing
- Funding Instructions:
- Read:
- Scope: ''
- Endpoint: https://api.stripe.com/v1/issuing/funding_instructions
- Method: GET
- Payload: ''
- Valid:
- - 200
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/funding_instructions/list
- Note: On 403, it mistakently says "rak_topup_read"
- Write:
- Scope: ''
- Endpoint: https://api.stripe.com/v1/issuing/funding_instructions
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/funding_instructions/create
- Note: Same as read but says "write"
- PaymentIntents:
- Read:
- Scope: rak_payment_intent_read
- Endpoint: https://api.stripe.com/v1/payment_intents
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/payment_intents/list
- Note: ''
- Write:
- Scope: rak_payment_intent_write
- Endpoint: https://api.stripe.com/v1/payment_intents
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/payment_intents/create
- Note: ''
- PaymentMethods:
- Read:
- Scope: rak_payment_method_read
- Endpoint: https://api.stripe.com/v1/payment_methods
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_payment_methods/GetPaymentMethods
- Note: ''
- Write:
- Scope: rak_payment_method_write
- Endpoint: https://api.stripe.com/v1/payment_methods/nowaycanthisexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_payment_methods_payment_method_/PostPaymentMethodsPaymentMethod
- Note: ''
- Payment Method Domains:
- Read:
- Scope: ''
- Endpoint: https://api.stripe.com/v1/payment_method_domains
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/payment_method_domains/list
- Note: ''
- Write:
- Scope: ''
- Endpoint: https://api.stripe.com/v1/payment_method_domains
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/payment_method_domains/create
- Note: ''
- Payouts:
- Read:
- Scope: rak_payout_read
- Endpoint: https://api.stripe.com/v1/payouts
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/payouts/list
- Note: ''
- Write:
- Scope: rak_payout_write
- Endpoint: https://api.stripe.com/v1/payouts
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/payouts/create
- Note: ''
- Products:
- Read:
- Scope: rak_product_read
- Endpoint: https://api.stripe.com/v1/products
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/products/list
- Note: ''
- Write:
- Scope: rak_product_write
- Endpoint: https://api.stripe.com/v1/products
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/products/create
- Note: ''
- Shipping Rates:
- Read:
- Scope: rak_shipping_rate_read
- Endpoint: https://api.stripe.com/v1/shipping_rates
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/shipping_rates/list
- Note: ''
- Write:
- Scope: rak_shipping_rate_write
- Endpoint: https://api.stripe.com/v1/shipping_rates
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/shipping_rates/create
- Note: ''
- SetupIntents:
- Read:
- Scope: rak_setup_intent_read
- Endpoint: https://api.stripe.com/v1/setup_intents
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/setup_intents/list
- Note: ''
- Write:
- Scope: rak_setup_intent_write
- Endpoint: https://api.stripe.com/v1/setup_intents/nowaycanthisexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/setup_intents/create
- Note: ''
- Sources:
- Read:
- Scope: rak_source_read
- Endpoint: https://api.stripe.com/v1/sources/nowaycanthisexist
- Method: GET
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/sources/retrieve
- Note: ''
- Write:
- Scope: rak_source_write
- Endpoint: https://api.stripe.com/v1/sources/nowaycanthisexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/sources/update
- Note: ''
- Tokens:
- Read:
- Scope: rak_token_read
- Endpoint: https://api.stripe.com/v1/tokens/nowaycanthisexist
- Method: GET
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/tokens/retrieve
- Note: ''
- Write:
- Scope: rak_token_write
- Endpoint: https://api.stripe.com/v1/tokens
- Method: POST
- Payload: '"card[number]"=4242424242424242'
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/tokens/create_card
- Note: ''
- Checkout:
- Checkout Sessions:
- Read:
- Scope: rak_checkout_session_read
- Endpoint: https://api.stripe.com/v1/checkout/sessions
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/checkout/sessions/list
- Note: ''
- Write:
- Scope: rak_checkout_session_write
- Endpoint: https://api.stripe.com/v1/checkout/sessions
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/checkout/sessions/create
- Note: ''
- Billing:
- Coupons:
- Read:
- Scope: rak_coupon_read
- Endpoint: https://api.stripe.com/v1/coupons
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/coupons/list
- Note: ''
- Write:
- Scope: rak_coupon_write
- Endpoint: https://api.stripe.com/v1/coupons
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/coupons/create
- Note: ''
- Promotion Codes:
- Read:
- Scope: rak_promotion_code_read
- Endpoint: https://api.stripe.com/v1/promotion_codes
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/promotion_codes/list
- Note: ''
- Write:
- Scope: rak_promotion_code_write
- Endpoint: https://api.stripe.com/v1/promotion_codes
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/promotion_codes/create
- Note: ''
- Credit notes:
- Read:
- Scope: rak_credit_note_read
- Endpoint: https://api.stripe.com/v1/credit_notes
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/credit_notes/list
- Note: ''
- Write:
- Scope: rak_credit_note_write
- Endpoint: https://api.stripe.com/v1/credit_notes/nowaythiscanexsit
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/credit_notes/update
- Note: ''
- Customer portal:
- Read:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: ''
- Write:
- Scope: rak_customer_portal_write
- Endpoint: https://api.stripe.com/v1/billing_portal/sessions
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/customer_portal/sessions/create
- Note: ''
- Invoices:
- Read:
- Scope: ''
- Endpoint: https://api.stripe.com/v1/invoices
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/invoices/list
- Note: Wrong scope in error message.
- Write:
- Scope: rak_invoice_write
- Endpoint: https://api.stripe.com/v1/invoices
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/invoices/create
- Note: ''
- Prices:
- Read:
- Scope: rak_plan_read
- Endpoint: https://api.stripe.com/v1/prices
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/prices/list
- Note: ''
- Write:
- Scope: rak_plan_write
- Endpoint: https://api.stripe.com/v1/prices
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/prices/create
- Note: ''
- Subscriptions:
- Read:
- Scope: rak_subscription_read
- Endpoint: https://api.stripe.com/v1/subscriptions
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/subscriptions/list
- Note: ''
- Write:
- Scope: rak_subscription_write
- Endpoint: https://api.stripe.com/v1/subscriptions
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/subscriptions/create
- Note: ''
- Quote:
- Read:
- Scope: rak_quote_read
- Endpoint: https://api.stripe.com/v1/quotes
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/quotes/list
- Note: ''
- Write:
- Scope: rak_quote_write
- Endpoint: https://api.stripe.com/v1/quotes/nowaythiscanexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/quotes/update
- Note: ''
- Tax IDs:
- Read:
- Scope: rak_tax_id_read
- Endpoint: https://api.stripe.com/v1/tax_ids
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/tax_ids/list
- Note: ''
- Write:
- Scope: rak_tax_id_write
- Endpoint: https://api.stripe.com/v1/tax_ids
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/tax_ids/create
- Note: ''
- Tax Rates:
- Read:
- Scope: rak_tax_rate_read
- Endpoint: https://api.stripe.com/v1/tax_rates
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/tax_rates/list
- Note: ''
- Write:
- Scope: rak_tax_rate_write
- Endpoint: https://api.stripe.com/v1/tax_rates
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/tax_rates/create
- Note: ''
- Usage Records:
- Read:
- Scope: rak_usage_record_read
- Endpoint: https://api.stripe.com/v1/subscription_items/nowaythiscanexist/usage_record_summaries
- Method: GET
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/usage_records/subscription_item_summary_list
- Note: ''
- Write:
- Scope: rak_usage_record_write
- Endpoint: https://api.stripe.com/v1/subscription_items/nowaythiscanexist/usage_records
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/usage_records/create
- Note: ''
- Meters:
- Read:
- Scope: rak_billing_meter_read
- Endpoint: https://api.stripe.com/v1/billing/meters
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/billing/meter/list
- Note: ''
- Write:
- Scope: rak_billing_meter_write
- Endpoint: https://api.stripe.com/v1/billing/meters
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/billing/meter/create
- Note: ''
- Meter Events:
- Read:
- Scope: rak_billing_meter_event_read
- Endpoint: https://api.stripe.com/v1/billing/meters/nowaythiscanexist/event_summaries
- Method: GET
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/billing/meter-event_summary/list
- Note: ''
- Write:
- Scope: rak_billing_meter_event_write
- Endpoint: https://api.stripe.com/v1/billing/meter_events
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/billing/meter-event/create
- Note: ''
- Meter Event Adjustments:
- Write:
- Scope: rak_billing_meter_event_adjustment_write
- Endpoint: https://api.stripe.com/v1/billing/meter_event_adjustments
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/billing/meter-event_adjustment/create
- Note: ''
- Connect:
- Application Fees:
- Read:
- Scope: rak_application_fee_read
- Endpoint: https://api.stripe.com/v1/application_fees
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/application_fees/list
- Note: ''
- Write:
- Scope: rak_application_fee_write
- Endpoint: https://api.stripe.com/v1/application_fees/nowaythiscanexist/refunds
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/fee_refunds/create
- Note: ''
- Login Links:
- Write:
- Scope: rak_edit_link_write
- Endpoint: https://api.stripe.com/v1/account/login_links
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_account_login_links/PostAccountLoginLinks
- Note: ''
- Account Links:
- Write:
- Scope: rak_account_link_write
- Endpoint: https://api.stripe.com/v1/account_links
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/account_links
- Note: ''
- Top-ups:
- Read:
- Scope: rak_topup_read
- Endpoint: https://api.stripe.com/v1/topups
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/topups/list
- Note: ''
- Write:
- Scope: rak_topup_write
- Endpoint: https://api.stripe.com/v1/topups
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/topups/create
- Note: ''
- Transfers:
- Read:
- Scope: rak_transfer_read
- Endpoint: https://api.stripe.com/v1/transfers
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/transfers/list
- Note: ''
- Write:
- Scope: rak_transfer_write
- Endpoint: https://api.stripe.com/v1/transfers
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/transfers/create
- Note: ''
- Orders:
- Orders:
- Read:
- Scope: rak_order_read
- Endpoint: https://api.stripe.com/v1/orders
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_orders/GetOrders
- Note: ''
- Write:
- Scope: rak_order_write
- Endpoint: https://api.stripe.com/v1/orders
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_orders/PostOrders
- Note: ''
- SKUs:
- Read:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: Seems like any key has 200 over these.
- Write:
- Scope: rak_sku_write
- Endpoint: https://api.stripe.com/v1/skus
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_skus/PostSkus
- Note: ''
- Issuing:
- Authorizations:
- Read:
- Scope: rak_issuing_authorization_read
- Endpoint: https://api.stripe.com/v1/issuing/authorizations/nowaythiscanexist
- Method: GET
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/authorizations/retrieve
- Note: ''
- Write:
- Scope: rak_issuing_authorization_write
- Endpoint: https://api.stripe.com/v1/issuing/authorizations/nowaythiscanexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/authorizations/update
- Note: ''
- Cardholders:
- Read:
- Scope: rak_issuing_cardholder_read
- Endpoint: https://api.stripe.com/v1/issuing/cardholders/nowaythiscanexist
- Method: GET
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/cardholders/retrieve
- Note: ''
- Write:
- Scope: rak_issuing_cardholder_write
- Endpoint: https://api.stripe.com/v1/issuing/cardholders
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/cardholders/create
- Note: ''
- Cards:
- Read:
- Scope: rak_issuing_card_read
- Endpoint: https://api.stripe.com/v1/issuing/cards/nowaythiscanexist
- Method: GET
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/cards/retrieve
- Note: ''
- Write:
- Scope: rak_issuing_card_write
- Endpoint: https://api.stripe.com/v1/issuing/cards
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/cards/create
- Note: ''
- Disputes:
- Read:
- Scope: rak_issuing_dispute_read
- Endpoint: https://api.stripe.com/v1/issuing/disputes/nowaythiscanexist
- Method: GET
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/disputes/retrieve
- Note: ''
- Write:
- Scope: rak_issuing_dispute_write
- Endpoint: https://api.stripe.com/v1/issuing/disputes/nowaythiscanexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/disputes/update
- Note: ''
- Tokens:
- Read:
- Scope: rak_issuing_network_token_read
- Endpoint: https://api.stripe.com/v1/issuing/tokens/nowaythiscanexist
- Method: GET
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/tokens/retrieve
- Note: ''
- Write:
- Scope: rak_issuing_network_token_write
- Endpoint: https://api.stripe.com/v1/issuing/tokens/nowaythiscanexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/tokens/update
- Note: ''
- Token Network Data:
- Read:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: ''
- Transactions:
- Read:
- Scope: rak_issuing_transaction_read
- Endpoint: https://api.stripe.com/v1/issuing/transactions/nowaythiscanexist
- Method: GET
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/transactions/retrieve
- Note: ''
- Write:
- Scope: rak_issuing_transaction_write
- Endpoint: https://api.stripe.com/v1/issuing/transactions/nowaythiscanexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/issuing/transactions/update
- Note: ''
- Reporting:
- Report Runs and Report Types:
- Read:
- Scope: rak_financial_statement_read
- Endpoint: https://api.stripe.com/v1/reporting/report_runs
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/reporting/report_run/list
- Note: ''
- Identity:
- Verification Sessions and Reports:
- Read:
- Scope: rak_identity_product_read
- Endpoint: https://api.stripe.com/v1/identity/verification_sessions
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/identity/verification_sessions/list
- Note: ''
- Write:
- Scope: rak_identity_product_write
- Endpoint: https://api.stripe.com/v1/identity/verification_sessions
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/identity/verification_sessions/create
- Note: ''
- Access recent detailed verification results:
- Read:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: Skip for now b/c requires account with data
- Access all detailed verification results:
- Read:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: Skip for now b/c requires account with data + this one requires IP allowlisting
- Webhook:
- Webhook Endpoints:
- Read:
- Scope: rak_webhook_read
- Endpoint: https://api.stripe.com/v1/webhook_endpoints
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/webhook_endpoints/list
- Note: ''
- Write:
- Scope: rak_webhook_write
- Endpoint: https://api.stripe.com/v1/webhook_endpoints
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/webhook_endpoints/create
- Note: ''
- Stripe CLI:
- Debugging tools:
- Write:
- Scope: ''
- Endpoint: ''
- Method: ''
- Payload: ''
- Valid: []
- Invalid: []
- Docs: Can't find a relevant endpoint
- Note: ''
- Payment Links:
- Payment Links:
- Read:
- Scope: rak_payment_links_read
- Endpoint: https://api.stripe.com/v1/payment_links
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/payment_links/payment_links/list
- Note: ''
- Write:
- Scope: rak_payment_links_write
- Endpoint: https://api.stripe.com/v1/payment_links
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/payment_links/payment_links/create
- Note: ''
- Terminal:
- Configurations:
- Read:
- Scope: rak_terminal_configuration_read
- Endpoint: https://api.stripe.com/v1/terminal/configurations
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/terminal/configuration/list
- Note: ''
- Write:
- Scope: rak_terminal_configuration_write
- Endpoint: https://api.stripe.com/v1/terminal/configurations/nowaythiscanexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/terminal/configuration/update
- Note: ''
- Locations:
- Read:
- Scope: rak_terminal_location_read
- Endpoint: https://api.stripe.com/v1/terminal/locations
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/terminal/locations/list
- Note: ''
- Write:
- Scope: rak_terminal_location_write
- Endpoint: https://api.stripe.com/v1/terminal/locations
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/terminal/locations/create
- Note: ''
- Readers:
- Read:
- Scope: rak_terminal_reader_read
- Endpoint: https://api.stripe.com/v1/terminal/readers
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/terminal/readers/list
- Note: ''
- Write:
- Scope: rak_terminal_reader_write
- Endpoint: https://api.stripe.com/v1/terminal/readers
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/terminal/readers/create
- Note: ''
- Connection Tokens:
- Write:
- Scope: rak_terminal_connection_token_write
- Endpoint: ''
- Method: POST
- Payload: ''
- Valid: []
- Invalid: []
- Docs: ''
- Note: Skip b/c requires a state change.
- Tax:
- Tax Calculations and Transactions:
- Read:
- Scope: rak_tax_transaction_read
- Endpoint: https://api.stripe.com/v1/tax/calculations/nowaycanthisexist/line_items
- Method: GET
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/tax/calculations/line_items
- Note: ''
- Write:
- Scope: rak_tax_transaction_write
- Endpoint: https://api.stripe.com/v1/tax/calculations
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/tax/calculations/create
- Note: ''
- Tax Settings and Registrations:
- Read:
- Scope: rak_tax_settings_read
- Endpoint: https://api.stripe.com/v1/tax/settings
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/tax/settings/retrieve
- Note: ''
- Write:
- Scope: rak_tax_settings_write
- Endpoint: https://api.stripe.com/v1/tax/registrations/nowaycanthisexist
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/tax/registrations/update
- Note: ''
- Radar:
- Reviews:
- Read:
- Scope: rak_review_read
- Endpoint: https://api.stripe.com/v1/reviews
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/radar/reviews/list
- Note: ''
- Write:
- Scope: rak_review_write
- Endpoint: https://api.stripe.com/v1/reviews/nowaycanthisexist/approve
- Method: POST
- Payload: ''
- Valid:
- - 404
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/radar/reviews/approve
- Note: ''
- Climate:
- Climate Orders:
- Read:
- Scope: rak_climate_order_read
- Endpoint: https://api.stripe.com/v1/climate/orders
- Method: GET
- Payload: ''
- Valid:
- - 200
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/climate/order/list
- Note: ''
- Write:
- Scope: rak_climate_order_write
- Endpoint: https://api.stripe.com/v1/climate/orders
- Method: POST
- Payload: ''
- Valid:
- - 400
- Invalid:
- - 403
- Docs: https://docs.stripe.com/api/climate/order/create
- Note: ''
diff --git a/pkg/analyzer/analyzers/stripe/stripe.go b/pkg/analyzer/analyzers/stripe/stripe.go
deleted file mode 100644
index 2cd4b4e03b09..000000000000
--- a/pkg/analyzer/analyzers/stripe/stripe.go
+++ /dev/null
@@ -1,384 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go stripe
-
-package stripe
-
-import (
- "bytes"
- _ "embed"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "os"
- "sort"
- "strings"
-
- "github.com/fatih/color"
- "github.com/jedib0t/go-pretty/v6/table"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "gopkg.in/yaml.v2"
-)
-
-var _ analyzers.Analyzer = (*Analyzer)(nil)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeStripe }
-
-func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credInfo["key"]
- if !ok {
- return nil, errors.New("key not found in credentialInfo")
- }
-
- info, err := AnalyzePermissions(a.Cfg, key)
- if err != nil {
- return nil, err
- }
- return secretInfoToAnalyzerResult(info), nil
-}
-
-func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
- if info == nil {
- return nil
- }
- result := &analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeStripe,
- Metadata: map[string]any{
- "key_type": info.KeyType,
- "key_env": info.KeyEnv,
- },
- }
-
- // create list of bindings using permissions, with category being the parent and unbounded resource
- result.Bindings = []analyzers.Binding{}
- result.UnboundedResources = []analyzers.Resource{}
- for _, permissionCategory := range info.Permissions {
- parentResource := &analyzers.Resource{
- Name: permissionCategory.Name,
- FullyQualifiedName: permissionCategory.Name,
- Type: "category",
- Metadata: nil,
- Parent: nil,
- }
- if len(permissionCategory.Permissions) == 0 {
- result.UnboundedResources = append(result.UnboundedResources, *parentResource)
- } else {
- for _, permission := range permissionCategory.Permissions {
- if _, ok := StringToPermission[*permission.Value]; !ok { // skip unknown scopes/permission
- continue
- }
- result.Bindings = append(result.Bindings, analyzers.Binding{
- Resource: *parentResource,
- Permission: analyzers.Permission{
- Value: fmt.Sprintf("%s:%s", permission.Name, *permission.Value),
- },
- })
- }
- }
- }
-
- return result
-
-}
-
-const (
- SECRET_PREFIX = "sk_"
- PUBLISHABLE_PREFIX = "pk_"
- RESTRICTED_PREFIX = "rk_"
- LIVE_PREFIX = "live_"
- TEST_PREFIX = "test_"
- SECRET = "Secret"
- PUBLISHABLE = "Publishable"
- RESTRICTED = "Restricted"
- LIVE = "Live"
- TEST = "Test"
-)
-
-//go:embed restricted.yaml
-var restrictedConfig []byte
-
-type PermissionStruct struct {
- Name string
- Value *string
-}
-
-type PermissionsCategory struct {
- Name string
- Permissions []PermissionStruct
-}
-
-type HttpStatusTest struct {
- Endpoint string `yaml:"Endpoint"`
- Method string `yaml:"Method"`
- Payload interface{} `yaml:"Payload"`
- ValidStatuses []int `yaml:"Valid"`
- InvalidStatuses []int `yaml:"Invalid"`
-}
-
-type Category map[string]map[string]HttpStatusTest
-
-type Config struct {
- Categories map[string]Category `yaml:"categories"`
-}
-
-type SecretInfo struct {
- KeyType string
- KeyEnv string
- Valid bool
- Permissions []PermissionsCategory
-}
-
-func (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string) (bool, error) {
- // If body data, marshal to JSON
- var data io.Reader
- if h.Payload != nil {
- jsonData, err := json.Marshal(h.Payload)
- if err != nil {
- return false, err
- }
- data = bytes.NewBuffer(jsonData)
- }
-
- // Create new HTTP request
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest(h.Method, h.Endpoint, data)
- if err != nil {
- return false, err
- }
-
- // Add custom headers if provided
- for key, value := range headers {
- req.Header.Set(key, value)
- }
-
- // Execute HTTP Request
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer resp.Body.Close()
-
- // Check response status code
- switch {
- case StatusContains(resp.StatusCode, h.ValidStatuses):
- return true, nil
- case StatusContains(resp.StatusCode, h.InvalidStatuses):
- return false, nil
- default:
- fmt.Println(h)
- fmt.Println(resp.Body)
- fmt.Println(resp.StatusCode)
- return false, errors.New("error checking response status code")
- }
-}
-
-func StatusContains(status int, vals []int) bool {
- for _, v := range vals {
- if status == v {
- return true
- }
- }
- return false
-}
-
-func checkKeyType(key string) (string, error) {
- if strings.HasPrefix(key, SECRET_PREFIX) {
- return SECRET, nil
- } else if strings.HasPrefix(key, PUBLISHABLE_PREFIX) {
- return PUBLISHABLE, nil
- } else if strings.HasPrefix(key, RESTRICTED_PREFIX) {
- return RESTRICTED, nil
- }
- return "", errors.New("Invalid Stripe key format")
-}
-
-func checkKeyEnv(key string) (string, error) {
- //remove first 3 characters
- key = key[3:]
- if strings.HasPrefix(key, LIVE_PREFIX) {
- return LIVE, nil
- }
- if strings.HasPrefix(key, TEST_PREFIX) {
- return TEST, nil
- }
- return "", errors.New("invalid Stripe key format")
-}
-
-func checkValidity(cfg *config.Config, key string) (bool, error) {
- // Create a new request
- client := analyzers.NewAnalyzeClient(cfg)
- req, err := http.NewRequest("GET", "https://api.stripe.com/v1/charges", nil)
- if err != nil {
- return false, err
- }
-
- // Add Authorization header
- req.Header.Add("Authorization", "Bearer "+key)
-
- // Send the request
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer resp.Body.Close()
-
- // Check the response. Valid is 200 (secret/restricted) or 403 (restricted)
- if resp.StatusCode == 200 || resp.StatusCode == 403 {
- return true, nil
- }
- return false, nil
-}
-
-func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
- // Check if secret, publishable, or restricted key
- var keyType, keyEnv string
- keyType, err := checkKeyType(key)
- if err != nil {
- return nil, err
- }
-
- // Check if live or test key
- keyEnv, err = checkKeyEnv(key)
- if err != nil {
- return nil, err
- }
-
- // Check if key is valid
- valid, err := checkValidity(cfg, key)
- if err != nil {
- return nil, err
- }
-
- permissions, err := getRestrictedPermissions(cfg, key)
- if err != nil {
- return nil, err
- }
- // Additional details
- // get total customers
- // get total charges
-
- return &SecretInfo{
- KeyType: keyType,
- KeyEnv: keyEnv,
- Valid: valid,
- Permissions: permissions,
- }, nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
- info, err := AnalyzePermissions(cfg, key)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
-
- if info.KeyType == PUBLISHABLE {
- color.Red("[x] This is a publishable Stripe key. It is not considered secret.")
- return
- }
-
- if !info.Valid {
- color.Red("[x] Invalid Stripe API Key\n")
- return
- }
-
- color.Green("[!] Valid Stripe API Key\n\n")
-
- if info.KeyType == SECRET {
- color.Green("[i] Key Type: %s", info.KeyType)
- } else if info.KeyType == RESTRICTED {
- color.Yellow("[i] Key Type: %s", info.KeyType)
- }
-
- if info.KeyEnv == LIVE {
- color.Green("[i] Key Environment: %s", info.KeyEnv)
- } else if info.KeyEnv == TEST {
- color.Red("[i] Key Environment: %s", info.KeyEnv)
- }
-
- fmt.Println("")
-
- if info.KeyType == SECRET {
- color.Green("[i] Permissions: Full Access")
- return
- }
-
- printRestrictedPermissions(info.Permissions, cfg.ShowAll)
-}
-
-func getRestrictedPermissions(cfg *config.Config, key string) ([]PermissionsCategory, error) {
- var config Config
- if err := yaml.Unmarshal(restrictedConfig, &config); err != nil {
- fmt.Println("Error unmarshalling YAML:", err)
- return nil, err
- }
-
- output := make([]PermissionsCategory, 0)
-
- for category, scopes := range config.Categories {
- permissions := make([]PermissionStruct, 0)
- for name, scope := range scopes {
- value := ""
- testCount := 0
- for typ, test := range scope {
- if test.Endpoint == "" {
- continue
- }
- testCount++
- status, err := test.RunTest(cfg, map[string]string{"Authorization": "Bearer " + key})
- if err != nil {
- color.Red("[x] Error running test: %s", err.Error())
- return nil, err
- }
- if status {
- value = typ
- }
- if value == "Write" {
- break
- }
- }
- if testCount > 0 {
- permissions = append(permissions, PermissionStruct{Name: name, Value: &value})
- }
- }
- output = append(output, PermissionsCategory{Name: category, Permissions: permissions})
- }
-
- // sort the output
- order := []string{"Core", "Checkout", "Billing", "Connect", "Orders", "Issuing", "Reporting", "Identity", "Webhook", "Stripe CLI", "Payment Links", "Terminal", "Tax", "Radar", "Climate"}
- // ToDo: order the permissions within each category
-
- // Create a map for quick lookup of the order
- orderMap := make(map[string]int)
- for i, name := range order {
- orderMap[name] = i
- }
-
- // Sort the categories according to the desired order
- sort.Slice(output, func(i, j int) bool {
- return orderMap[output[i].Name] < orderMap[output[j].Name]
- })
-
- return output, nil
-
-}
-
-func printRestrictedPermissions(permissions []PermissionsCategory, show_all bool) {
- t := table.NewWriter()
- t.SetOutputMirror(os.Stdout)
- t.AppendHeader(table.Row{"Category", "Permission", "Access"})
- for _, category := range permissions {
- for _, permission := range category.Permissions {
- if *permission.Value != "" || show_all {
- t.AppendRow([]interface{}{category.Name, permission.Name, *permission.Value})
- }
- }
- }
- t.Render()
-}
diff --git a/pkg/analyzer/analyzers/stripe/stripe_test.go b/pkg/analyzer/analyzers/stripe/stripe_test.go
deleted file mode 100644
index 4fd6b2e3dbb8..000000000000
--- a/pkg/analyzer/analyzers/stripe/stripe_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package stripe
-
-import (
- _ "embed"
- "encoding/json"
- "sort"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-//go:embed expected_output.json
-var expectedOutput []byte
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- key string
- want []byte // JSON string
- wantErr bool
- }{
- {
- name: "valid Stripe restricted key",
- key: testSecrets.MustGetField("STRIPE_SECRET"),
- want: expectedOutput,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{Cfg: &config.Config{}}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(got.Bindings)
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Bindings need to be in the same order to be comparable
- sortBindings(wantObj.Bindings)
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented, wantIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- wantIndented, err = json.MarshalIndent(wantObj, "", " ")
- if err != nil {
- t.Fatalf("could not marshal want to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
- }
- })
- }
-}
-
-// Helper function to sort bindings
-func sortBindings(bindings []analyzers.Binding) {
- sort.SliceStable(bindings, func(i, j int) bool {
- if bindings[i].Resource.Name == bindings[j].Resource.Name {
- return bindings[i].Permission.Value < bindings[j].Permission.Value
- }
- return bindings[i].Resource.Name < bindings[j].Resource.Name
- })
-}
diff --git a/pkg/analyzer/analyzers/twilio/permissions.go b/pkg/analyzer/analyzers/twilio/permissions.go
deleted file mode 100644
index 601ca9b0c3ce..000000000000
--- a/pkg/analyzer/analyzers/twilio/permissions.go
+++ /dev/null
@@ -1,136 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package twilio
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
- AccountManagementRead Permission = iota
- AccountManagementWrite Permission = iota
- SubaccountConfigurationRead Permission = iota
- SubaccountConfigurationWrite Permission = iota
- KeyManagementRead Permission = iota
- KeyManagementWrite Permission = iota
- ServiceVerificationRead Permission = iota
- ServiceVerificationWrite Permission = iota
- SmsRead Permission = iota
- SmsWrite Permission = iota
- VoiceRead Permission = iota
- VoiceWrite Permission = iota
- MessagingRead Permission = iota
- MessagingWrite Permission = iota
- CallManagementRead Permission = iota
- CallManagementWrite Permission = iota
-)
-
-var (
- PermissionStrings = map[Permission]string{
- AccountManagementRead: "account_management:read",
- AccountManagementWrite: "account_management:write",
- SubaccountConfigurationRead: "subaccount_configuration:read",
- SubaccountConfigurationWrite: "subaccount_configuration:write",
- KeyManagementRead: "key_management:read",
- KeyManagementWrite: "key_management:write",
- ServiceVerificationRead: "service_verification:read",
- ServiceVerificationWrite: "service_verification:write",
- SmsRead: "sms:read",
- SmsWrite: "sms:write",
- VoiceRead: "voice:read",
- VoiceWrite: "voice:write",
- MessagingRead: "messaging:read",
- MessagingWrite: "messaging:write",
- CallManagementRead: "call_management:read",
- CallManagementWrite: "call_management:write",
- }
-
- StringToPermission = map[string]Permission{
- "account_management:read": AccountManagementRead,
- "account_management:write": AccountManagementWrite,
- "subaccount_configuration:read": SubaccountConfigurationRead,
- "subaccount_configuration:write": SubaccountConfigurationWrite,
- "key_management:read": KeyManagementRead,
- "key_management:write": KeyManagementWrite,
- "service_verification:read": ServiceVerificationRead,
- "service_verification:write": ServiceVerificationWrite,
- "sms:read": SmsRead,
- "sms:write": SmsWrite,
- "voice:read": VoiceRead,
- "voice:write": VoiceWrite,
- "messaging:read": MessagingRead,
- "messaging:write": MessagingWrite,
- "call_management:read": CallManagementRead,
- "call_management:write": CallManagementWrite,
- }
-
- PermissionIDs = map[Permission]int{
- AccountManagementRead: 1,
- AccountManagementWrite: 2,
- SubaccountConfigurationRead: 3,
- SubaccountConfigurationWrite: 4,
- KeyManagementRead: 5,
- KeyManagementWrite: 6,
- ServiceVerificationRead: 7,
- ServiceVerificationWrite: 8,
- SmsRead: 9,
- SmsWrite: 10,
- VoiceRead: 11,
- VoiceWrite: 12,
- MessagingRead: 13,
- MessagingWrite: 14,
- CallManagementRead: 15,
- CallManagementWrite: 16,
- }
-
- IdToPermission = map[int]Permission{
- 1: AccountManagementRead,
- 2: AccountManagementWrite,
- 3: SubaccountConfigurationRead,
- 4: SubaccountConfigurationWrite,
- 5: KeyManagementRead,
- 6: KeyManagementWrite,
- 7: ServiceVerificationRead,
- 8: ServiceVerificationWrite,
- 9: SmsRead,
- 10: SmsWrite,
- 11: VoiceRead,
- 12: VoiceWrite,
- 13: MessagingRead,
- 14: MessagingWrite,
- 15: CallManagementRead,
- 16: CallManagementWrite,
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
diff --git a/pkg/analyzer/analyzers/twilio/permissions.yaml b/pkg/analyzer/analyzers/twilio/permissions.yaml
deleted file mode 100644
index cc03fa5d2e55..000000000000
--- a/pkg/analyzer/analyzers/twilio/permissions.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-permissions:
- - account_management:read
- - account_management:write
- - subaccount_configuration:read
- - subaccount_configuration:write
- - key_management:read
- - key_management:write
- - service_verification:read
- - service_verification:write
- - sms:read
- - sms:write
- - voice:read
- - voice:write
- - messaging:read
- - messaging:write
- - call_management:read
- - call_management:write
diff --git a/pkg/analyzer/analyzers/twilio/twilio.go b/pkg/analyzer/analyzers/twilio/twilio.go
deleted file mode 100644
index e09eb9c503f1..000000000000
--- a/pkg/analyzer/analyzers/twilio/twilio.go
+++ /dev/null
@@ -1,308 +0,0 @@
-//go:generate generate_permissions permissions.yaml permissions.go twilio
-
-package twilio
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
-
- "github.com/fatih/color"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-type Analyzer struct {
- Cfg *config.Config
-}
-
-func (a *Analyzer) Type() analyzers.AnalyzerType {
- return analyzers.AnalyzerTypeTwilio
-}
-
-func (a *Analyzer) Analyze(ctx context.Context, credentialInfo map[string]string) (*analyzers.AnalyzerResult, error) {
- key, ok := credentialInfo["key"]
- if !ok {
- return nil, errors.New("key not found in credentialInfo")
- }
-
- sid, ok := credentialInfo["sid"]
- if !ok {
- return nil, errors.New("sid not found in credentialInfo")
- }
-
- if a.Cfg == nil {
- a.Cfg = &config.Config{} // You might need to adjust this based on how you want to handle config
- }
-
- info, err := AnalyzePermissions(a.Cfg, sid, key)
- if err != nil {
- return nil, err
- }
-
- // List parent and subaccounts
- accounts, err := listTwilioAccounts(a.Cfg, sid, key)
- if err != nil {
- return nil, err
- }
-
- var permissions []Permission
- if info.AccountStatusCode == 200 {
- permissions = []Permission{
- AccountManagementRead,
- AccountManagementWrite,
- SubaccountConfigurationRead,
- SubaccountConfigurationWrite,
- KeyManagementRead,
- KeyManagementWrite,
- ServiceVerificationRead,
- ServiceVerificationWrite,
- SmsRead,
- SmsWrite,
- VoiceRead,
- VoiceWrite,
- MessagingRead,
- MessagingWrite,
- CallManagementRead,
- CallManagementWrite,
- }
- } else if info.AccountStatusCode == 401 {
- permissions = []Permission{
- ServiceVerificationRead,
- ServiceVerificationWrite,
- SmsRead,
- SmsWrite,
- VoiceRead,
- VoiceWrite,
- MessagingRead,
- MessagingWrite,
- CallManagementRead,
- CallManagementWrite,
- }
- }
-
- var (
- bindings []analyzers.Binding
- parentAccountSID = ""
- parentAccountFriendlyName = ""
- )
-
- if len(info.ServicesRes.Services) > 0 {
- parentAccountSID = info.ServicesRes.Services[0].AccountSID
- parentAccountFriendlyName = info.ServicesRes.Services[0].FriendlyName
- }
-
- for _, account := range accounts {
- accountType := "Account"
- if parentAccountSID != "" && account.SID != parentAccountSID {
- accountType = "SubAccount"
- }
- resource := analyzers.Resource{
- Name: account.FriendlyName,
- FullyQualifiedName: "twilio.com/account/" + account.SID,
- Type: accountType,
- }
- if parentAccountSID != "" && account.SID != parentAccountSID {
- resource.Parent = &analyzers.Resource{
- Name: parentAccountFriendlyName,
- FullyQualifiedName: "twilio.com/account/" + parentAccountSID,
- Type: "Account",
- }
- }
-
- for _, perm := range permissions {
- permStr, _ := perm.ToString()
- bindings = append(bindings, analyzers.Binding{
- Resource: resource,
- Permission: analyzers.Permission{
- Value: permStr,
- },
- })
- }
- }
-
- return &analyzers.AnalyzerResult{
- AnalyzerType: analyzers.AnalyzerTypeTwilio,
- Bindings: bindings,
- }, nil
-}
-
-type secretInfo struct {
- ServicesRes serviceResponse
- AccountStatusCode int
-}
-
-const (
- AUTHENTICATED_NO_PERMISSION = 70051
- INVALID_CREDENTIALS = 20003
-)
-
-// getAccountsStatusCode returns the status code from the Accounts endpoint
-// this is used to determine whether the key is scoped as main or standard, since standard has no access here.
-func getAccountsStatusCode(cfg *config.Config, sid string, secret string) (int, error) {
- // create http client
- client := analyzers.NewAnalyzeClient(cfg)
-
- // create request
- req, err := http.NewRequest("GET", "https://api.twilio.com/2010-04-01/Accounts", nil)
- if err != nil {
- return 0, err
- }
-
- // add basicAuth
- req.SetBasicAuth(sid, secret)
-
- // send request
- resp, err := client.Do(req)
- if err != nil {
- return 0, err
- }
- defer resp.Body.Close()
- return resp.StatusCode, nil
-}
-
-type serviceResponse struct {
- Code int `json:"code"`
- Services []service `json:"services"`
-}
-
-type service struct {
- FriendlyName string `json:"friendly_name"` // friendly name of a service
- SID string `json:"sid"` // object id of service
- AccountSID string `json:"account_sid"` // account sid
-}
-
-// getVerifyServicesStatusCode returns the status code and the JSON response from the Verify Services endpoint
-// only the code value is captured in the JSON response and this is only shown when the key is invalid or has no permissions
-func getVerifyServicesStatusCode(cfg *config.Config, sid string, secret string) (serviceResponse, error) {
- var serviceRes serviceResponse
-
- // create http client
- client := analyzers.NewAnalyzeClient(cfg)
-
- // create request
- req, err := http.NewRequest("GET", "https://verify.twilio.com/v2/Services", nil)
- if err != nil {
- return serviceRes, err
- }
-
- // add basicAuth
- req.SetBasicAuth(sid, secret)
-
- // send request
- resp, err := client.Do(req)
- if err != nil {
- return serviceRes, err
- }
- defer resp.Body.Close()
-
- // read response
- if err := json.NewDecoder(resp.Body).Decode(&serviceRes); err != nil {
- return serviceRes, err
- }
-
- return serviceRes, nil
-}
-
-func listTwilioAccounts(cfg *config.Config, sid, secret string) ([]service, error) {
- // create http client
- client := analyzers.NewAnalyzeClient(cfg)
-
- // create request
- req, err := http.NewRequest("GET", "https://api.twilio.com/2010-04-01/Accounts.json", nil)
- if err != nil {
- return nil, err
- }
-
- // add basicAuth
- req.SetBasicAuth(sid, secret)
-
- // send request
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- var result struct {
- Accounts []service `json:"accounts"`
- }
-
- // read response
- if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
- return nil, err
- }
-
- return result.Accounts, nil
-}
-
-func AnalyzePermissions(cfg *config.Config, sid, secret string) (*secretInfo, error) {
- servicesRes, err := getVerifyServicesStatusCode(cfg, sid, secret)
- if err != nil {
- return nil, err
- }
-
- statusCode, err := getAccountsStatusCode(cfg, sid, secret)
- if err != nil {
- return nil, err
- }
-
- return &secretInfo{
- ServicesRes: servicesRes,
- AccountStatusCode: statusCode,
- }, nil
-}
-
-func AnalyzeAndPrintPermissions(cfg *config.Config, sid, secret string) {
- info, err := AnalyzePermissions(cfg, sid, secret)
- if err != nil {
- color.Red("[x] Error: %s", err.Error())
- return
- }
-
- if info.ServicesRes.Code == INVALID_CREDENTIALS {
- color.Red("[x] Invalid Twilio API Key")
- return
- }
-
- if info.ServicesRes.Code == AUTHENTICATED_NO_PERMISSION {
- printRestrictedKeyMsg()
- return
- }
-
- printPermissions(info.AccountStatusCode)
-}
-
-// printPermissions prints the permissions based on the status code
-// 200 means the key is main, 401 means the key is standard
-func printPermissions(statusCode int) {
-
- if statusCode != 200 && statusCode != 401 {
- color.Red("[x] Invalid Twilio API Key")
- return
- }
-
- color.Green("[!] Valid Twilio API Key\n")
- color.Green("[i] Expires: Never")
-
- if statusCode == 401 {
- color.Yellow("[i] Key type: Standard")
- color.Yellow("[i] Permissions: All EXCEPT key management and account/subaccount configuration.")
-
- } else if statusCode == 200 {
- color.Green("[i] Key type: Main (aka Admin)")
- color.Green("[i] Permissions: All")
- }
-}
-
-// printRestrictedKeyMsg prints the message for a restricted key
-// this is a temporary measure since the restricted key type is still in beta
-func printRestrictedKeyMsg() {
- color.Green("[!] Valid Twilio API Key\n")
- color.Green("[i] Expires: Never")
- color.Yellow("[i] Key type: Restricted")
- color.Yellow("[i] Permissions: Limited")
- fmt.Println("[*] Note: Twilio is rolling out a Restricted API Key type, which provides fine-grained control over API endpoints. Since it's still in a Public Beta, this has not been incorporated into this tool.")
-}
diff --git a/pkg/analyzer/analyzers/twilio/twilio_test.go b/pkg/analyzer/analyzers/twilio/twilio_test.go
deleted file mode 100644
index 2d4b16048f5b..000000000000
--- a/pkg/analyzer/analyzers/twilio/twilio_test.go
+++ /dev/null
@@ -1,290 +0,0 @@
-package twilio
-
-import (
- "encoding/json"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-func TestAnalyzer_Analyze(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tests := []struct {
- name string
- sid string
- key string
- want string // JSON string
- wantErr bool
- }{
- {
- name: "valid Twilio key",
- sid: testSecrets.MustGetField("TWILLIO_ID"),
- key: testSecrets.MustGetField("TWILLIO_API"),
- want: ` {
- "AnalyzerType": 20,
- "Bindings": [
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "account_management:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "account_management:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "subaccount_configuration:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "subaccount_configuration:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "key_management:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "key_management:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "service_verification:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "service_verification:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "sms:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "sms:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "voice:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "voice:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "messaging:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "messaging:write",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "call_management:read",
- "Parent": null
- }
- },
- {
- "Resource": {
- "Name": "My first Twilio account",
- "FullyQualifiedName": "twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5",
- "Type": "Account",
- "Metadata": null,
- "Parent": null
- },
- "Permission": {
- "Value": "call_management:write",
- "Parent": null
- }
- }
- ],
- "UnboundedResources": null,
- "Metadata": null
- }`,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Analyzer{}
- got, err := a.Analyze(ctx, map[string]string{"key": tt.key, "sid": tt.sid})
- if (err != nil) != tt.wantErr {
- t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Marshal the actual result to JSON
- gotJSON, err := json.Marshal(got)
- if err != nil {
- t.Fatalf("could not marshal got to JSON: %s", err)
- }
-
- // Parse the expected JSON string
- var wantObj analyzers.AnalyzerResult
- if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
- t.Fatalf("could not unmarshal want JSON string: %s", err)
- }
-
- // Marshal the expected result to JSON (to normalize)
- wantJSON, err := json.Marshal(wantObj)
- if err != nil {
- t.Fatalf("could not marshal want to JSON: %s", err)
- }
-
- // Compare the JSON strings
- if string(gotJSON) != string(wantJSON) {
- // Pretty-print both JSON strings for easier comparison
- var gotIndented []byte
- gotIndented, err = json.MarshalIndent(got, "", " ")
- if err != nil {
- t.Fatalf("could not marshal got to indented JSON: %s", err)
- }
- t.Errorf("Analyzer.Analyze() = \n%s", gotIndented)
- }
- })
- }
-}
diff --git a/pkg/analyzer/cli.go b/pkg/analyzer/cli.go
deleted file mode 100644
index f4e626f701d6..000000000000
--- a/pkg/analyzer/cli.go
+++ /dev/null
@@ -1,155 +0,0 @@
-package analyzer
-
-import (
- "strings"
-
- "github.com/alecthomas/kingpin/v2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airbrake"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airtable/airtableoauth"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airtable/airtablepat"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/anthropic"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/asana"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/databricks"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/datadog"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/digitalocean"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/dockerhub"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/dropbox"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/elevenlabs"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/fastly"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/figma"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/gitlab"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/groq"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/huggingface"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/jira"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/launchdarkly"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mailchimp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mailgun"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/monday"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mux"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mysql"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/netlify"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/ngrok"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/notion"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/openai"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/opsgenie"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/plaid"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/planetscale"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/postgres"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/posthog"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/postman"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/privatekey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/sendgrid"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/shopify"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/slack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/sourcegraph"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/square"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/stripe"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/twilio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
-)
-
-type SecretInfo struct {
- Parts map[string]string
- Cfg *config.Config
-}
-
-func Command(app *kingpin.Application) *kingpin.CmdClause {
- return app.Command("analyze", "Analyze API keys for fine-grained permissions information.")
-}
-
-func Run(keyType string, secretInfo SecretInfo) {
- if secretInfo.Cfg == nil {
- secretInfo.Cfg = &config.Config{}
- }
- switch strings.ToLower(keyType) {
- case "github":
- github.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "sendgrid":
- sendgrid.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "openai":
- openai.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "postgres":
- postgres.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "mysql":
- mysql.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "slack":
- slack.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "twilio":
- twilio.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["sid"], secretInfo.Parts["key"])
- case "airbrake":
- airbrake.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "huggingface":
- huggingface.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "stripe":
- stripe.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "gitlab":
- gitlab.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "mailchimp":
- mailchimp.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "postman":
- postman.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "bitbucket":
- bitbucket.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "asana":
- asana.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "mailgun":
- mailgun.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "square":
- square.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "sourcegraph":
- sourcegraph.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "shopify":
- shopify.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"], secretInfo.Parts["url"])
- case "opsgenie":
- opsgenie.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "privatekey":
- privatekey.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "notion":
- notion.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "dockerhub":
- dockerhub.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["username"], secretInfo.Parts["pat"])
- case "anthropic":
- anthropic.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "digitalocean":
- digitalocean.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "elevenlabs":
- elevenlabs.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "planetscale":
- planetscale.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["id"], secretInfo.Parts["token"])
- case "airtableoauth":
- airtableoauth.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "airtablepat":
- airtablepat.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "groq":
- groq.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "launchdarkly":
- launchdarkly.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "figma":
- figma.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "plaid":
- plaid.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["secret"], secretInfo.Parts["id"], secretInfo.Parts["token"])
- case "netlify":
- netlify.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "fastly":
- fastly.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "monday":
- monday.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "datadog":
- datadog.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["apiKey"], secretInfo.Parts["appKey"])
- case "ngrok":
- ngrok.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "mux":
- mux.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"], secretInfo.Parts["secret"])
- case "posthog":
- posthog.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "dropbox":
- dropbox.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
- case "databricks":
- databricks.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["domain"], secretInfo.Parts["token"])
- case "jira":
- jira.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["domain"], secretInfo.Parts["email"], secretInfo.Parts["token"])
- }
-}
diff --git a/pkg/analyzer/config/config.go b/pkg/analyzer/config/config.go
deleted file mode 100644
index 4efd88cbccb0..000000000000
--- a/pkg/analyzer/config/config.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package config
-
-// TODO: separate CLI configuration from analysis configuration.
-type Config struct {
- LoggingEnabled bool
- LogFile string
- ShowAll bool
- // Limit API calls when enumerating permissions.
- Shallow bool
-}
diff --git a/pkg/analyzer/generate_permissions/generate_permissions.go b/pkg/analyzer/generate_permissions/generate_permissions.go
deleted file mode 100644
index c26b61e41f93..000000000000
--- a/pkg/analyzer/generate_permissions/generate_permissions.go
+++ /dev/null
@@ -1,146 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "os"
- "regexp"
- "strings"
- "text/template"
-
- "golang.org/x/text/cases"
- "golang.org/x/text/language"
- "gopkg.in/yaml.v3"
-)
-
-type PermissionsData struct {
- Permissions []string `yaml:"permissions"`
- PackageName string `yaml:"package_name"`
-}
-
-const templateText = `// Code generated by go generate; DO NOT EDIT.
-package {{ .PackageName }}
-
-import "errors"
-
-type Permission int
-
-const (
- Invalid Permission = iota
-{{- range $index, $permission := .Permissions }}
- {{ ToCamelCase $permission }} Permission = iota
-{{- end }}
-)
-
-var (
- PermissionStrings = map[Permission]string{
-{{- range $index, $permission := .Permissions }}
- {{ ToCamelCase $permission }}: "{{ $permission }}",
-{{- end }}
- }
-
- StringToPermission = map[string]Permission{
-{{- range $index, $permission := .Permissions }}
- "{{ $permission }}": {{ ToCamelCase $permission }},
-{{- end }}
- }
-
- PermissionIDs = map[Permission]int{
-{{- range $index, $permission := .Permissions }}
- {{ ToCamelCase $permission }}: {{ inc $index }},
-{{- end }}
- }
-
- IdToPermission = map[int]Permission{
-{{- range $index, $permission := .Permissions }}
- {{ inc $index }}: {{ ToCamelCase $permission }},
-{{- end }}
- }
-)
-
-// ToString converts a Permission enum to its string representation
-func (p Permission) ToString() (string, error) {
- if str, ok := PermissionStrings[p]; ok {
- return str, nil
- }
- return "", errors.New("invalid permission")
-}
-
-// ToID converts a Permission enum to its ID
-func (p Permission) ToID() (int, error) {
- if id, ok := PermissionIDs[p]; ok {
- return id, nil
- }
- return 0, errors.New("invalid permission")
-}
-
-// PermissionFromString converts a string representation to its Permission enum
-func PermissionFromString(s string) (Permission, error) {
- if p, ok := StringToPermission[s]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission string")
-}
-
-// PermissionFromID converts an ID to its Permission enum
-func PermissionFromID(id int) (Permission, error) {
- if p, ok := IdToPermission[id]; ok {
- return p, nil
- }
- return 0, errors.New("invalid permission ID")
-}
-`
-
-// ToCamelCase converts a string to CamelCase
-func ToCamelCase(s string) string {
- parts := strings.Split(s, ":")
- caser := cases.Title(language.English)
- for i := range parts {
- subParts := regexp.MustCompile(`[\_\.\-]+`).Split(parts[i], -1)
- for j := range subParts {
- subParts[j] = caser.String(subParts[j])
- }
- parts[i] = strings.Join(subParts, "")
- }
- return strings.Join(parts, "")
-}
-
-func main() {
- // Read the YAML file from first argument
- file, err := os.Open(os.Args[1])
- if err != nil {
- log.Fatalf("Failed to open YAML file: %v", err)
- }
- defer file.Close()
-
- var data PermissionsData
- decoder := yaml.NewDecoder(file)
- err = decoder.Decode(&data)
- if err != nil {
- log.Fatalf("Failed to decode YAML file: %v", err)
- }
- data.PackageName = os.Args[3]
-
- // Parse the template
- tmpl, err := template.New("permissions").Funcs(template.FuncMap{
- "ToCamelCase": ToCamelCase,
- "inc": func(i int) int { return i + 1 },
- }).Parse(templateText)
- if err != nil {
- log.Fatalf("Failed to parse template: %v", err)
- }
-
- // Generate the code
- outputFile, err := os.Create(os.Args[2])
- if err != nil {
- log.Fatalf("Failed to create output file: %v", err)
- }
- defer outputFile.Close()
-
- err = tmpl.Execute(outputFile, data)
- if err != nil {
- log.Fatalf("Failed to execute template: %v", err)
- }
-
- fmt.Println("Permissions code generated successfully.")
-}
diff --git a/pkg/buffers/buffer/buffer.go b/pkg/buffers/buffer/buffer.go
deleted file mode 100644
index b3bd17749d6c..000000000000
--- a/pkg/buffers/buffer/buffer.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// Package buffer provides a custom buffer type that includes metrics for tracking buffer usage.
-// It also provides a pool for managing buffer reusability.
-package buffer
-
-import (
- "bytes"
- "io"
- "time"
-)
-
-// Buffer is a wrapper around bytes.Buffer that includes a timestamp for tracking Buffer checkout duration.
-type Buffer struct {
- *bytes.Buffer
- checkedOutAt time.Time
-}
-
-const defaultBufferSize = 1 << 12 // 4KB
-// NewBuffer creates a new instance of Buffer.
-func NewBuffer() *Buffer { return &Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, defaultBufferSize))} }
-
-func (b *Buffer) Grow(size int) {
- b.Buffer.Grow(size)
- b.recordGrowth(size)
-}
-
-func (b *Buffer) ResetMetric() { b.checkedOutAt = time.Now() }
-
-func (b *Buffer) RecordMetric() {
- dur := time.Since(b.checkedOutAt)
- checkoutDuration.Observe(float64(dur.Microseconds()))
- checkoutDurationTotal.Add(float64(dur.Microseconds()))
- totalBufferSize.Add(float64(b.Cap()))
- totalBufferLength.Add(float64(b.Len()))
-}
-
-func (b *Buffer) recordGrowth(size int) {
- growCount.Inc()
- growAmount.Add(float64(size))
-}
-
-// Write date to the buffer.
-func (b *Buffer) Write(data []byte) (int, error) {
- if b.Buffer == nil {
- // This case should ideally never occur if buffers are properly managed.
- b.Buffer = bytes.NewBuffer(make([]byte, 0, defaultBufferSize))
- b.ResetMetric()
- }
-
- size := len(data)
- bufferLength := b.Buffer.Len()
- totalSizeNeeded := bufferLength + size
-
- // If the total size is within the threshold, write to the buffer.
- availableSpace := b.Buffer.Cap() - bufferLength
- growSize := totalSizeNeeded - bufferLength
- if growSize > availableSpace {
- // We are manually growing the buffer so we can track the growth via metrics.
- // Knowing the exact data size, we directly resize to fit it, rather than exponential growth
- // which may require multiple allocations and copies if the size required is much larger
- // than double the capacity. Our approach aligns with default behavior when growth sizes
- // happen to match current capacity, retaining asymptotic efficiency benefits.
- b.Grow(growSize)
- }
-
- return b.Buffer.Write(data)
-}
-
-// Compile time check to make sure readCloser implements io.ReadSeekCloser.
-var _ io.ReadSeekCloser = (*readCloser)(nil)
-
-// readCloser is a custom implementation of io.ReadCloser. It wraps a bytes.Reader
-// for reading data from an in-memory buffer and includes an onClose callback.
-// The onClose callback is used to return the buffer to the pool, ensuring buffer re-usability.
-type readCloser struct {
- *bytes.Reader
- onClose func()
-}
-
-// ReadCloser creates a new instance of readCloser.
-func ReadCloser(data []byte, onClose func()) *readCloser {
- return &readCloser{Reader: bytes.NewReader(data), onClose: onClose}
-}
-
-// Close implements the io.Closer interface. It calls the onClose callback to return the buffer
-// to the pool, enabling buffer reuse. This method should be called by the consumers of ReadCloser
-// once they have finished reading the data to ensure proper resource management.
-func (brc *readCloser) Close() error {
- if brc.onClose == nil {
- return nil
- }
-
- brc.onClose() // Return the buffer to the pool
- brc.Reader = nil
- return nil
-}
-
-// Read reads up to len(p) bytes into p from the underlying reader.
-// It returns the number of bytes read and any error encountered.
-// On reaching the end of the available data, it returns 0 and io.EOF.
-// Calling Read on a closed reader will also return 0 and io.EOF.
-func (brc *readCloser) Read(p []byte) (int, error) {
- if brc.Reader == nil {
- return 0, io.EOF
- }
-
- return brc.Reader.Read(p)
-}
diff --git a/pkg/buffers/buffer/buffer_test.go b/pkg/buffers/buffer/buffer_test.go
deleted file mode 100644
index f25189890c5d..000000000000
--- a/pkg/buffers/buffer/buffer_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package buffer
-
-import (
- "bytes"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestBufferWrite(t *testing.T) {
- t.Parallel()
- tests := []struct {
- name string
- initialCapacity int
- writeDataSequence [][]byte // Sequence of writes to simulate multiple writes
- expectedSize int
- expectedCap int
- }{
- {
- name: "Write to empty buffer",
- initialCapacity: defaultBufferSize,
- writeDataSequence: [][]byte{
- []byte("hello"),
- },
- expectedSize: 5,
- expectedCap: defaultBufferSize, // No growth for small data
- },
- {
- name: "Write causing growth",
- initialCapacity: 10, // Small initial capacity to force growth
- writeDataSequence: [][]byte{
- []byte("this is a longer string exceeding initial capacity"),
- },
- expectedSize: 50,
- expectedCap: 50,
- },
- {
- name: "Write nil data",
- initialCapacity: defaultBufferSize,
- writeDataSequence: [][]byte{nil},
- expectedCap: defaultBufferSize,
- },
- {
- name: "Repeated writes, cumulative growth",
- initialCapacity: 20, // Set an initial capacity to test growth over multiple writes
- writeDataSequence: [][]byte{
- []byte("first write, "),
- []byte("second write, "),
- []byte("third write exceeding the initial capacity."),
- },
- expectedSize: 70,
- expectedCap: 70, // Expect capacity to grow to accommodate all writes
- },
- {
- name: "Write large single data to test significant growth",
- initialCapacity: 50, // Set an initial capacity smaller than the data to be written
- writeDataSequence: [][]byte{
- bytes.Repeat([]byte("a"), 1024), // 1KB data to significantly exceed initial capacity
- },
- expectedSize: 1024,
- expectedCap: 1024,
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- buf := &Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, tc.initialCapacity))}
- totalWritten := 0
- for _, data := range tc.writeDataSequence {
- n, err := buf.Write(data)
- assert.NoError(t, err)
-
- totalWritten += n
- }
- assert.Equal(t, tc.expectedSize, totalWritten)
- assert.Equal(t, tc.expectedSize, buf.Len())
- assert.GreaterOrEqual(t, buf.Cap(), tc.expectedCap)
- })
- }
-}
-
-func TestReadCloserClose(t *testing.T) {
- t.Parallel()
- onCloseCalled := false
- rc := ReadCloser([]byte("data"), func() { onCloseCalled = true })
-
- err := rc.Close()
- assert.NoError(t, err)
- assert.True(t, onCloseCalled, "onClose callback should be called upon Close")
-}
diff --git a/pkg/buffers/buffer/metrics.go b/pkg/buffers/buffer/metrics.go
deleted file mode 100644
index 4a22580ebeb8..000000000000
--- a/pkg/buffers/buffer/metrics.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package buffer
-
-import (
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promauto"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
-)
-
-var (
- growCount = promauto.NewCounter(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "grow_count",
- Help: "Total number of times buffers in the pool have grown.",
- })
-
- growAmount = promauto.NewCounter(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "grow_amount",
- Help: "Total amount of bytes buffers in the pool have grown by.",
- })
-
- checkoutDurationTotal = promauto.NewCounter(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "checkout_duration_total_us",
- Help: "Total duration in microseconds of Buffer checkouts.",
- })
-
- checkoutDuration = promauto.NewHistogram(prometheus.HistogramOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "checkout_duration_us",
- Help: "Duration in microseconds of Buffer checkouts.",
- Buckets: prometheus.ExponentialBuckets(10, 10, 7),
- })
-
- totalBufferLength = promauto.NewGauge(prometheus.GaugeOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "total_buffer_length",
- Help: "Total length of all buffers combined.",
- })
-
- totalBufferSize = promauto.NewGauge(prometheus.GaugeOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "total_buffer_size",
- Help: "Total size of all buffers combined.",
- })
-)
diff --git a/pkg/buffers/pool/metrics.go b/pkg/buffers/pool/metrics.go
deleted file mode 100644
index 3d4de5410611..000000000000
--- a/pkg/buffers/pool/metrics.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package pool
-
-import (
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promauto"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
-)
-
-var (
- activeBufferCount = promauto.NewGauge(prometheus.GaugeOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "active_buffer_count",
- Help: "Current number of active buffers.",
- })
-
- bufferCount = promauto.NewGauge(prometheus.GaugeOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "buffer_count",
- Help: "Total number of buffers managed by the pool.",
- })
-
- shrinkCount = promauto.NewCounter(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "shrink_count",
- Help: "Total number of times buffers in the pool have shrunk.",
- })
-
- shrinkAmount = promauto.NewCounter(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "shrink_amount",
- Help: "Total amount of bytes buffers in the pool have shrunk by.",
- })
-
- checkoutCount = promauto.NewCounter(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "checkout_count",
- Help: "Total number of Buffer checkouts.",
- })
-)
diff --git a/pkg/buffers/pool/pool.go b/pkg/buffers/pool/pool.go
deleted file mode 100644
index d3e5c82d806e..000000000000
--- a/pkg/buffers/pool/pool.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package pool
-
-import (
- "bytes"
- "sync"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/buffers/buffer"
-)
-
-type poolMetrics struct{}
-
-func (poolMetrics) recordShrink(amount int) {
- shrinkCount.Inc()
- shrinkAmount.Add(float64(amount))
-}
-
-func (poolMetrics) recordBufferRetrival() {
- activeBufferCount.Inc()
- checkoutCount.Inc()
- bufferCount.Inc()
-}
-
-func (poolMetrics) recordBufferReturn(buf *buffer.Buffer) {
- activeBufferCount.Dec()
- buf.RecordMetric()
-}
-
-// Pool of buffers.
-type Pool struct {
- *sync.Pool
- bufferSize int
-
- metrics poolMetrics
-}
-
-const defaultBufferSize = 1 << 12 // 4KB
-// NewBufferPool creates a new instance of BufferPool.
-func NewBufferPool(size int) *Pool {
- pool := &Pool{bufferSize: size}
-
- pool.Pool = &sync.Pool{
- New: func() any {
- return &buffer.Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, pool.bufferSize))}
- },
- }
-
- return pool
-}
-
-// Get returns a Buffer from the pool.
-func (p *Pool) Get() *buffer.Buffer {
- buf, ok := p.Pool.Get().(*buffer.Buffer)
- if !ok {
- buf = &buffer.Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, p.bufferSize))}
- }
- p.metrics.recordBufferRetrival()
- buf.ResetMetric()
-
- return buf
-}
-
-// Put returns a Buffer to the pool.
-func (p *Pool) Put(buf *buffer.Buffer) {
- p.metrics.recordBufferReturn(buf)
-
- // If the Buffer is more than twice the default size, replace it with a new Buffer.
- // This prevents us from returning very large buffers to the pool.
- const maxAllowedCapacity = 2 * defaultBufferSize
- if buf.Cap() > int(maxAllowedCapacity) {
- p.metrics.recordShrink(buf.Cap() - defaultBufferSize)
- buf = &buffer.Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, p.bufferSize))}
- } else {
- // Reset the Buffer to clear any existing data.
- buf.Reset()
- }
-
- p.Pool.Put(buf)
-}
diff --git a/pkg/buffers/pool/pool_test.go b/pkg/buffers/pool/pool_test.go
deleted file mode 100644
index e322cb70725f..000000000000
--- a/pkg/buffers/pool/pool_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package pool
-
-import (
- "bytes"
- "testing"
-
- "github.com/stretchr/testify/assert"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/buffers/buffer"
-)
-
-func TestNewBufferPool(t *testing.T) {
- t.Parallel()
- tests := []struct {
- name string
- size int
- expectedBuffSize int
- }{
- {name: "Default pool size", size: defaultBufferSize, expectedBuffSize: defaultBufferSize},
- {
- name: "Custom pool size",
- size: 8 * 1024,
- expectedBuffSize: 8 * 1024,
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- pool := NewBufferPool(tc.size)
- assert.Equal(t, tc.expectedBuffSize, pool.bufferSize)
- })
- }
-}
-
-func TestBufferPoolGetPut(t *testing.T) {
- t.Parallel()
- tests := []struct {
- name string
- preparePool func(p *Pool) *buffer.Buffer // Prepare the pool and return an initial buffer to put if needed
- expectedCapBefore int // Expected capacity before putting it back
- expectedCapAfter int // Expected capacity after retrieving it again
- }{
- {
- name: "Get new buffer and put back without modification",
- preparePool: func(_ *Pool) *buffer.Buffer {
- return nil // No initial buffer to put
- },
- expectedCapBefore: defaultBufferSize,
- expectedCapAfter: defaultBufferSize,
- },
- {
- name: "Put oversized buffer, expect shrink",
- preparePool: func(p *Pool) *buffer.Buffer {
- buf := &buffer.Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, 3*defaultBufferSize))}
- return buf
- },
- expectedCapBefore: defaultBufferSize,
- expectedCapAfter: defaultBufferSize, // Should shrink back to default
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- pool := NewBufferPool(defaultBufferSize)
- initialBuf := tc.preparePool(pool)
- if initialBuf != nil {
- pool.Put(initialBuf)
- }
-
- buf := pool.Get()
- assert.Equal(t, tc.expectedCapBefore, buf.Cap())
-
- pool.Put(buf)
-
- bufAfter := pool.Get()
- assert.Equal(t, tc.expectedCapAfter, bufAfter.Cap())
- })
- }
-}
diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go
deleted file mode 100644
index 4ca6955fd492..000000000000
--- a/pkg/cache/cache.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// Package cache provides an interface which can be implemented by different cache types.
-package cache
-
-// Cache is used to store key/value pairs.
-type Cache[T any] interface {
- // Set stores the given key/value pair.
- Set(key string, val T)
- // Get returns the value for the given key and a boolean indicating if the key was found.
- Get(key string) (T, bool)
- // Exists returns true if the given key exists in the cache.
- Exists(key string) bool
- // Delete the given key from the cache.
- Delete(key string)
- // Clear all key/value pairs from the cache.
- Clear()
- // Count the number of key/value pairs in the cache.
- Count() int
- // Keys returns all keys in the cache.
- Keys() []string
- // Values returns all values in the cache.
- Values() []T
- // Contents returns all keys in the cache encoded as a string.
- Contents() string
-}
diff --git a/pkg/cache/decorator.go b/pkg/cache/decorator.go
deleted file mode 100644
index 61dde6b8c03c..000000000000
--- a/pkg/cache/decorator.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package cache
-
-// WithMetrics is a decorator that adds metrics collection to any Cache implementation.
-type WithMetrics[T any] struct {
- wrapped Cache[T]
- metrics BaseMetricsCollector
- cacheName string
-}
-
-// NewCacheWithMetrics creates a new WithMetrics decorator that wraps the provided Cache
-// and collects metrics using the provided BaseMetricsCollector.
-// The cacheName parameter is used to identify the cache in the collected metrics.
-func NewCacheWithMetrics[T any](wrapped Cache[T], metrics BaseMetricsCollector, cacheName string) *WithMetrics[T] {
- return &WithMetrics[T]{
- wrapped: wrapped,
- metrics: metrics,
- cacheName: cacheName,
- }
-}
-
-// Set sets the value for the given key in the cache. It also records a set metric
-// for the cache using the provided metrics collector and cache name.
-func (c *WithMetrics[T]) Set(key string, val T) {
- c.metrics.RecordSet(c.cacheName)
- c.wrapped.Set(key, val)
-}
-
-// Get retrieves the value for the given key from the underlying cache. It also records
-// a hit or miss metric for the cache using the provided metrics collector and cache name.
-func (c *WithMetrics[T]) Get(key string) (T, bool) {
- val, found := c.wrapped.Get(key)
- if found {
- c.metrics.RecordHit(c.cacheName)
- } else {
- c.metrics.RecordMiss(c.cacheName)
- }
- return val, found
-}
-
-// Exists checks if the given key exists in the cache. It records a hit or miss metric
-// for the cache using the provided metrics collector and cache name.
-func (c *WithMetrics[T]) Exists(key string) bool {
- found := c.wrapped.Exists(key)
- if found {
- c.metrics.RecordHit(c.cacheName)
- } else {
- c.metrics.RecordMiss(c.cacheName)
- }
- return found
-}
-
-// Delete removes the value for the given key from the cache. It also records a delete metric
-// for the cache using the provided metrics collector and cache name.
-func (c *WithMetrics[T]) Delete(key string) {
- c.wrapped.Delete(key)
- c.metrics.RecordDelete(c.cacheName)
-}
-
-// Clear removes all entries from the cache. It also records a clear metric
-// for the cache using the provided metrics collector and cache name.
-func (c *WithMetrics[T]) Clear() {
- c.wrapped.Clear()
- c.metrics.RecordClear(c.cacheName)
-}
-
-// Count returns the number of entries in the cache. It also records a count metric
-// for the cache using the provided metrics collector and cache name.
-func (c *WithMetrics[T]) Count() int {
- count := c.wrapped.Count()
- return count
-}
-
-// Keys returns all keys in the cache. It also records a keys metric
-// for the cache using the provided metrics collector and cache name.
-func (c *WithMetrics[T]) Keys() []string { return c.wrapped.Keys() }
-
-// Values returns all values in the cache. It also records a values metric
-// for the cache using the provided metrics collector and cache name.
-func (c *WithMetrics[T]) Values() []T { return c.wrapped.Values() }
-
-// Contents returns all keys in the cache as a string. It also records a contents metric
-// for the cache using the provided metrics collector and cache name.
-func (c *WithMetrics[T]) Contents() string { return c.wrapped.Contents() }
diff --git a/pkg/cache/decorator_test.go b/pkg/cache/decorator_test.go
deleted file mode 100644
index 781aa6dd315d..000000000000
--- a/pkg/cache/decorator_test.go
+++ /dev/null
@@ -1,379 +0,0 @@
-package cache
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
-)
-
-type mockCollector struct{ mock.Mock }
-
-func (m *mockCollector) RecordHits(cacheName string, hits uint64) { m.Called(cacheName, hits) }
-func (m *mockCollector) RecordMisses(cacheName string, misses uint64) { m.Called(cacheName, misses) }
-
-func (m *mockCollector) RecordSet(cacheName string) { m.Called(cacheName) }
-func (m *mockCollector) RecordHit(cacheName string) { m.Called(cacheName) }
-func (m *mockCollector) RecordMiss(cacheName string) { m.Called(cacheName) }
-func (m *mockCollector) RecordDelete(cacheName string) { m.Called(cacheName) }
-func (m *mockCollector) RecordClear(cacheName string) { m.Called(cacheName) }
-
-type mockCache[T any] struct{ mock.Mock }
-
-func (m *mockCache[T]) Set(key string, val T) { m.Called(key, val) }
-
-func (m *mockCache[T]) Get(key string) (T, bool) {
- args := m.Called(key)
- var zero T
- if args.Get(0) != nil {
- return args.Get(0).(T), args.Bool(1)
- }
- return zero, args.Bool(1)
-}
-
-func (m *mockCache[T]) Exists(key string) bool {
- args := m.Called(key)
- return args.Bool(0)
-}
-
-func (m *mockCache[T]) Delete(key string) { m.Called(key) }
-
-func (m *mockCache[T]) Clear() { m.Called() }
-
-func (m *mockCache[T]) Count() int {
- args := m.Called()
- return args.Int(0)
-}
-
-func (m *mockCache[T]) Keys() []string {
- args := m.Called()
- return args.Get(0).([]string)
-}
-
-func (m *mockCache[T]) Values() []T {
- args := m.Called()
- return args.Get(0).([]T)
-}
-
-func (m *mockCache[T]) Contents() string {
- args := m.Called()
- return args.String(0)
-}
-
-// setupCache initializes the mock cache and metrics collector, then wraps them with the WithMetrics decorator.
-func setupCache[T any](t *testing.T) (*WithMetrics[T], *mockCache[T], *mockCollector) {
- t.Helper()
-
- collector := new(mockCollector)
- cache := new(mockCache[T])
- wrappedCache := NewCacheWithMetrics[T](cache, collector, "test_cache")
- assert.NotNil(t, wrappedCache, "WithMetrics cache should not be nil")
-
- return wrappedCache, cache, collector
-}
-
-func TestNewLRUCache(t *testing.T) {
- c, _, _ := setupCache[int](t)
- assert.Equal(t, "test_cache", c.cacheName)
-}
-
-func TestCacheSet(t *testing.T) {
- c, cacheMock, collectorMock := setupCache[string](t)
-
- collectorMock.On("RecordSet", "test_cache").Once()
- cacheMock.On("Set", "key", "value").Once()
-
- c.Set("key", "value")
-
- collectorMock.AssertCalled(t, "RecordSet", "test_cache")
- cacheMock.AssertCalled(t, "Set", "key", "value")
-}
-
-func TestCacheGet(t *testing.T) {
- c, cacheMock, collectorMock := setupCache[string](t)
-
- collectorMock.On("RecordSet", "test_cache").Once()
- cacheMock.On("Set", "key", "value").Once()
-
- collectorMock.On("RecordHit", "test_cache").Once()
- cacheMock.On("Get", "key").Return("value", true).Once()
-
- collectorMock.On("RecordMiss", "test_cache").Once()
- cacheMock.On("Get", "non_existent").Return("", false).Once()
-
- c.Set("key", "value")
- collectorMock.AssertCalled(t, "RecordSet", "test_cache")
- cacheMock.AssertCalled(t, "Set", "key", "value")
-
- value, found := c.Get("key")
- assert.True(t, found, "Expected to find the key")
- assert.Equal(t, "value", value, "Expected value to match")
- collectorMock.AssertCalled(t, "RecordHit", "test_cache")
- cacheMock.AssertCalled(t, "Get", "key")
-
- _, found = c.Get("non_existent")
- assert.False(t, found, "Expected not to find the key")
- collectorMock.AssertCalled(t, "RecordMiss", "test_cache")
- cacheMock.AssertCalled(t, "Get", "non_existent")
-
- collectorMock.AssertExpectations(t)
- cacheMock.AssertExpectations(t)
-}
-
-func TestCacheExists(t *testing.T) {
- c, cacheMock, collectorMock := setupCache[string](t)
-
- collectorMock.On("RecordSet", "test_cache").Once()
- cacheMock.On("Set", "key", "value").Once()
-
- collectorMock.On("RecordHit", "test_cache").Once()
- cacheMock.On("Exists", "key").Return(true).Once()
-
- collectorMock.On("RecordMiss", "test_cache").Once()
- cacheMock.On("Exists", "non_existent").Return(false).Once()
-
- c.Set("key", "value")
- collectorMock.AssertCalled(t, "RecordSet", "test_cache")
- cacheMock.AssertCalled(t, "Set", "key", "value")
-
- exists := c.Exists("key")
- assert.True(t, exists, "Expected the key to exist")
- collectorMock.AssertCalled(t, "RecordHit", "test_cache")
- cacheMock.AssertCalled(t, "Exists", "key")
-
- exists = c.Exists("non_existent")
- assert.False(t, exists, "Expected the key not to exist")
- collectorMock.AssertCalled(t, "RecordMiss", "test_cache")
- cacheMock.AssertCalled(t, "Exists", "non_existent")
-
- collectorMock.AssertExpectations(t)
- cacheMock.AssertExpectations(t)
-}
-
-func TestCacheDelete(t *testing.T) {
- c, cacheMock, collectorMock := setupCache[string](t)
-
- collectorMock.On("RecordSet", "test_cache").Once()
- cacheMock.On("Set", "key", "value").Once()
-
- collectorMock.On("RecordDelete", "test_cache").Once()
- cacheMock.On("Delete", "key").Once()
-
- cacheMock.On("Get", "key").Return("", false).Once()
-
- collectorMock.On("RecordMiss", "test_cache").Once()
-
- c.Set("key", "value")
- collectorMock.AssertCalled(t, "RecordSet", "test_cache")
- cacheMock.AssertCalled(t, "Set", "key", "value")
-
- c.Delete("key")
- collectorMock.AssertCalled(t, "RecordDelete", "test_cache")
- cacheMock.AssertCalled(t, "Delete", "key")
-
- _, found := c.Get("key")
- assert.False(t, found, "Expected not to find the deleted key")
- collectorMock.AssertCalled(t, "RecordMiss", "test_cache")
- cacheMock.AssertCalled(t, "Get", "key")
-
- collectorMock.AssertExpectations(t)
- cacheMock.AssertExpectations(t)
-}
-
-func TestCacheClear(t *testing.T) {
- c, cacheMock, collectorMock := setupCache[string](t)
-
- collectorMock.On("RecordSet", "test_cache").Twice()
- cacheMock.On("Set", "key1", "value1").Once()
- cacheMock.On("Set", "key2", "value2").Once()
-
- collectorMock.On("RecordClear", "test_cache").Once()
- cacheMock.On("Clear").Once()
-
- cacheMock.On("Get", "key1").Return("", false).Once()
- cacheMock.On("Get", "key2").Return("", false).Once()
-
- c.Set("key1", "value1")
- c.Set("key2", "value2")
- collectorMock.AssertNumberOfCalls(t, "RecordSet", 2)
- cacheMock.AssertCalled(t, "Set", "key1", "value1")
- cacheMock.AssertCalled(t, "Set", "key2", "value2")
-
- c.Clear()
- collectorMock.AssertCalled(t, "RecordClear", "test_cache")
- cacheMock.AssertCalled(t, "Clear")
-
- collectorMock.On("RecordMiss", "test_cache").Twice()
- _, found1 := c.Get("key1")
- _, found2 := c.Get("key2")
- assert.False(t, found1, "Expected not to find key1 after clear")
- assert.False(t, found2, "Expected not to find key2 after clear")
- collectorMock.AssertNumberOfCalls(t, "RecordMiss", 2)
- cacheMock.AssertCalled(t, "Get", "key1")
- cacheMock.AssertCalled(t, "Get", "key2")
-
- collectorMock.AssertExpectations(t)
- cacheMock.AssertExpectations(t)
-}
-
-func TestCacheCount(t *testing.T) {
- c, cacheMock, collectorMock := setupCache[string](t)
-
- collectorMock.On("RecordSet", "test_cache").Times(3)
- cacheMock.On("Set", mock.Anything, mock.Anything).Times(3)
-
- cacheMock.On("Count").Return(3).Once()
-
- collectorMock.On("RecordDelete", "test_cache").Once()
- cacheMock.On("Delete", "key2").Once()
- cacheMock.On("Count").Return(2).Once()
-
- collectorMock.On("RecordClear", "test_cache").Once()
- cacheMock.On("Clear").Once()
- cacheMock.On("Count").Return(0).Once()
-
- c.Set("key1", "value1")
- c.Set("key2", "value2")
- c.Set("key3", "value3")
- assert.Equal(t, 3, c.Count(), "Expected count to be 3")
- collectorMock.AssertNumberOfCalls(t, "RecordSet", 3)
- cacheMock.AssertNumberOfCalls(t, "Set", 3)
- cacheMock.AssertCalled(t, "Count")
-
- c.Delete("key2")
- assert.Equal(t, 2, c.Count(), "Expected count to be 2 after deletion")
- collectorMock.AssertCalled(t, "RecordDelete", "test_cache")
- cacheMock.AssertCalled(t, "Delete", "key2")
- cacheMock.AssertCalled(t, "Count")
-
- c.Clear()
- assert.Equal(t, 0, c.Count(), "Expected count to be 0 after clear")
- collectorMock.AssertCalled(t, "RecordClear", "test_cache")
- cacheMock.AssertCalled(t, "Clear")
- cacheMock.AssertCalled(t, "Count")
-
- collectorMock.AssertExpectations(t)
- cacheMock.AssertExpectations(t)
-}
-
-func TestCacheKeys(t *testing.T) {
- c, cacheMock, collectorMock := setupCache[string](t)
-
- collectorMock.On("RecordSet", "test_cache").Times(3)
- cacheMock.On("Set", mock.Anything, mock.Anything).Times(3)
-
- collectorMock.On("RecordDelete", "test_cache").Once()
- cacheMock.On("Delete", "key2").Once()
- cacheMock.On("Clear").Once()
- collectorMock.On("RecordClear", "test_cache").Once()
-
- cacheMock.On("Keys").Return([]string{"key1", "key2", "key3"}).Once()
- cacheMock.On("Keys").Return([]string{"key1", "key3"}).Once()
- cacheMock.On("Keys").Return([]string{}).Once()
-
- c.Set("key1", "value1")
- c.Set("key2", "value2")
- c.Set("key3", "value3")
- collectorMock.AssertNumberOfCalls(t, "RecordSet", 3)
- cacheMock.AssertNumberOfCalls(t, "Set", 3)
-
- keys := c.Keys()
- assert.Len(t, keys, 3, "Expected 3 keys")
- assert.ElementsMatch(t, []string{"key1", "key2", "key3"}, keys, "Keys do not match expected values")
-
- c.Delete("key2")
- keys = c.Keys()
- assert.Len(t, keys, 2, "Expected 2 keys after deletion")
- assert.ElementsMatch(t, []string{"key1", "key3"}, keys, "Keys do not match expected values after deletion")
- collectorMock.AssertCalled(t, "RecordDelete", "test_cache")
-
- c.Clear()
- keys = c.Keys()
- assert.Len(t, keys, 0, "Expected no keys after clear")
- collectorMock.AssertCalled(t, "RecordClear", "test_cache")
-
- collectorMock.AssertExpectations(t)
- cacheMock.AssertExpectations(t)
-}
-
-func TestCacheValues(t *testing.T) {
- c, cacheMock, collectorMock := setupCache[string](t)
-
- collectorMock.On("RecordSet", "test_cache").Times(3)
- cacheMock.On("Set", mock.Anything, mock.Anything).Times(3)
-
- collectorMock.On("RecordDelete", "test_cache").Once()
- cacheMock.On("Delete", "key2").Once()
- collectorMock.On("RecordClear", "test_cache").Once()
- cacheMock.On("Clear").Once()
-
- cacheMock.On("Values").Return([]string{"value1", "value2", "value3"}).Once()
- cacheMock.On("Values").Return([]string{"value1", "value3"}).Once()
- cacheMock.On("Values").Return([]string{}).Once()
-
- c.Set("key1", "value1")
- c.Set("key2", "value2")
- c.Set("key3", "value3")
- collectorMock.AssertNumberOfCalls(t, "RecordSet", 3)
- cacheMock.AssertNumberOfCalls(t, "Set", 3)
-
- values := c.Values()
- assert.Len(t, values, 3, "Expected 3 values")
- assert.ElementsMatch(t, []string{"value1", "value2", "value3"}, values, "Values do not match expected values")
-
- c.Delete("key2")
- values = c.Values()
- assert.Len(t, values, 2, "Expected 2 values after deletion")
- assert.ElementsMatch(t, []string{"value1", "value3"}, values, "Values do not match expected values after deletion")
- collectorMock.AssertCalled(t, "RecordDelete", "test_cache")
-
- c.Clear()
- values = c.Values()
- assert.Len(t, values, 0, "Expected no values after clear")
- collectorMock.AssertCalled(t, "RecordClear", "test_cache")
-
- collectorMock.AssertExpectations(t)
- cacheMock.AssertExpectations(t)
-}
-
-func TestCacheContents(t *testing.T) {
- c, cacheMock, collectorMock := setupCache[string](t)
-
- collectorMock.On("RecordSet", "test_cache").Times(3)
- cacheMock.On("Set", mock.Anything, mock.Anything).Times(3)
-
- collectorMock.On("RecordDelete", "test_cache").Once()
- cacheMock.On("Delete", "key2").Once()
- collectorMock.On("RecordClear", "test_cache").Once()
- cacheMock.On("Clear").Once()
-
- cacheMock.On("Contents").Return("key1, key2, key3").Once()
- cacheMock.On("Contents").Return("key1, key3").Once()
- cacheMock.On("Contents").Return("[]").Once()
-
- c.Set("key1", "value1")
- c.Set("key2", "value2")
- c.Set("key3", "value3")
- collectorMock.AssertNumberOfCalls(t, "RecordSet", 3)
- cacheMock.AssertNumberOfCalls(t, "Set", 3)
-
- contents := c.Contents()
- assert.Contains(t, contents, "key1", "Contents should contain key1")
- assert.Contains(t, contents, "key2", "Contents should contain key2")
- assert.Contains(t, contents, "key3", "Contents should contain key3")
-
- c.Delete("key2")
- contents = c.Contents()
- assert.Contains(t, contents, "key1", "Contents should contain key1")
- assert.NotContains(t, contents, "key2", "Contents should not contain key2")
- assert.Contains(t, contents, "key3", "Contents should contain key3")
- collectorMock.AssertCalled(t, "RecordDelete", "test_cache")
-
- c.Clear()
- contents = c.Contents()
- assert.Equal(t, "[]", contents, "Contents should be empty after clear")
- collectorMock.AssertCalled(t, "RecordClear", "test_cache")
-
- collectorMock.AssertExpectations(t)
- cacheMock.AssertExpectations(t)
-}
diff --git a/pkg/cache/lru/lru.go b/pkg/cache/lru/lru.go
deleted file mode 100644
index e82d2999a45a..000000000000
--- a/pkg/cache/lru/lru.go
+++ /dev/null
@@ -1,122 +0,0 @@
-// Package lru provides a generic, size-limited, LRU (Least Recently Used) cache with optional
-// metrics collection and reporting. It wraps the golang-lru/v2 caching library, adding support for custom
-// metrics tracking cache hits, misses, evictions, and other cache operations.
-//
-// This package supports configuring key aspects of cache behavior, including maximum cache size,
-// and custom metrics collection.
-package lru
-
-import (
- "fmt"
-
- lru "github.com/hashicorp/golang-lru/v2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache"
-)
-
-// Cache is a generic LRU-sized cache that stores key-value pairs with a maximum size limit.
-// It wraps the lru.Cache library and adds support for custom metrics collection.
-type Cache[T any] struct {
- cache *lru.Cache[string, T]
-
- cacheName string
- capacity int
- evictMetrics cache.EvictionMetricsCollector
-}
-
-// Option defines a functional option for configuring the Cache.
-type Option[T any] func(*Cache[T])
-
-// WithCapacity is a functional option to set the maximum number of items the cache can hold.
-// If the capacity is not set, the default value (128_000) is used.
-func WithCapacity[T any](capacity int) Option[T] {
- return func(lc *Cache[T]) { lc.capacity = capacity }
-}
-
-// WithMetricsCollector is a functional option to set a custom metrics collector.
-func WithMetricsCollector[T any](collector cache.EvictionMetricsCollector) Option[T] {
- return func(lc *Cache[T]) { lc.evictMetrics = collector }
-}
-
-// NewCache creates a new Cache with optional configuration parameters.
-// It takes a cache name and a variadic list of options.
-func NewCache[T any](cacheName string, opts ...Option[T]) (*Cache[T], error) {
- // Default values for cache configuration.
- const defaultSize = 128_000
-
- sizedLRU := &Cache[T]{
- cacheName: cacheName,
- }
-
- for _, opt := range opts {
- opt(sizedLRU)
- }
-
- var onEvicted func(string, T)
- // Provide a evict callback function to record evictions if a custom metrics collector is provided.
- if sizedLRU.evictMetrics != nil {
- onEvicted = func(string, T) {
- sizedLRU.evictMetrics.RecordEviction(sizedLRU.cacheName)
- }
- }
-
- lcache, err := lru.NewWithEvict[string, T](defaultSize, onEvicted)
- if err != nil {
- return nil, fmt.Errorf("failed to create lrusized cache: %w", err)
- }
-
- sizedLRU.cache = lcache
-
- return sizedLRU, nil
-}
-
-// Set adds a key-value pair to the cache.
-func (lc *Cache[T]) Set(key string, val T) { lc.cache.Add(key, val) }
-
-// Get retrieves a value from the cache by key.
-func (lc *Cache[T]) Get(key string) (T, bool) {
- value, found := lc.cache.Get(key)
- if found {
- return value, true
- }
- var zero T
- return zero, false
-}
-
-// Exists checks if a key exists in the cache.
-func (lc *Cache[T]) Exists(key string) bool {
- _, found := lc.cache.Get(key)
- return found
-}
-
-// Delete removes a key from the cache.
-func (lc *Cache[T]) Delete(key string) {
- lc.cache.Remove(key)
-}
-
-// Clear removes all keys from the cache.
-func (lc *Cache[T]) Clear() {
- lc.cache.Purge()
-}
-
-// Count returns the number of key-value pairs in the cache.
-func (lc *Cache[T]) Count() int { return lc.cache.Len() }
-
-// Keys returns all keys in the cache.
-func (lc *Cache[T]) Keys() []string { return lc.cache.Keys() }
-
-// Values returns all values in the cache.
-func (lc *Cache[T]) Values() []T {
- items := lc.cache.Keys()
- res := make([]T, 0, len(items))
- for _, k := range items {
- v, _ := lc.cache.Get(k)
- res = append(res, v)
- }
- return res
-}
-
-// Contents returns all keys in the cache encoded as a string.
-func (lc *Cache[T]) Contents() string {
- return fmt.Sprintf("%v", lc.cache.Keys())
-}
diff --git a/pkg/cache/lru/lru_test.go b/pkg/cache/lru/lru_test.go
deleted file mode 100644
index 7c3c04ba5f60..000000000000
--- a/pkg/cache/lru/lru_test.go
+++ /dev/null
@@ -1,216 +0,0 @@
-package lru
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
-)
-
-type mockCollector struct{ mock.Mock }
-
-func (m *mockCollector) RecordEviction(cacheName string) { m.Called(cacheName) }
-
-// setupCache initializes the metrics and cache.
-// If withCollector is true, it sets up a cache with a custom metrics collector.
-// Otherwise, it sets up a cache without a custom metrics collector.
-func setupCache[T any](t *testing.T, withCollector bool) (*Cache[T], *mockCollector) {
- t.Helper()
-
- var collector *mockCollector
- var c *Cache[T]
- var err error
-
- if withCollector {
- collector = new(mockCollector)
- c, err = NewCache[T]("test_cache", WithMetricsCollector[T](collector))
- } else {
- c, err = NewCache[T]("test_cache")
- }
-
- assert.NoError(t, err, "Failed to create cache")
- assert.NotNil(t, c, "Cache should not be nil")
-
- return c, collector
-}
-
-func TestNewLRUCache(t *testing.T) {
- t.Run("default configuration", func(t *testing.T) {
- c, _ := setupCache[int](t, false)
- assert.Equal(t, "test_cache", c.cacheName)
- })
-
- t.Run("with custom max cost", func(t *testing.T) {
- c, _ := setupCache[int](t, false)
- assert.NotNil(t, c)
- })
-
- t.Run("with metrics collector", func(t *testing.T) {
- c, collector := setupCache[int](t, true)
- assert.NotNil(t, c)
- assert.Equal(t, "test_cache", c.cacheName)
- assert.Equal(t, collector, c.evictMetrics, "Cache metrics should match the collector")
- })
-}
-
-func TestCacheSet(t *testing.T) {
- c, _ := setupCache[string](t, true)
-
- c.Set("key", "value")
- value, found := c.Get("key")
- assert.True(t, found, "Expected to find the key")
- assert.Equal(t, "value", value, "Expected value to match")
-}
-
-func TestCacheGet(t *testing.T) {
- c, _ := setupCache[string](t, true)
-
- c.Set("key", "value")
-
- value, found := c.Get("key")
- assert.True(t, found, "Expected to find the key")
- assert.Equal(t, "value", value, "Expected value to match")
-
- _, found = c.Get("non_existent")
- assert.False(t, found, "Expected not to find the key")
-}
-
-func TestCacheExists(t *testing.T) {
- c, _ := setupCache[string](t, true)
-
- c.Set("key", "value")
-
- exists := c.Exists("key")
- assert.True(t, exists, "Expected the key to exist")
-
- exists = c.Exists("non_existent")
- assert.False(t, exists, "Expected the key not to exist")
-}
-
-func TestCacheDelete(t *testing.T) {
- c, collector := setupCache[string](t, true)
-
- collector.On("RecordEviction", "test_cache").Once()
-
- c.Set("key", "value")
-
- c.Delete("key")
- collector.AssertCalled(t, "RecordEviction", "test_cache")
-
- _, found := c.Get("key")
- assert.False(t, found, "Expected not to find the deleted key")
-}
-
-func TestCacheClear(t *testing.T) {
- c, collector := setupCache[string](t, true)
-
- collector.On("RecordEviction", "test_cache").Twice()
-
- c.Set("key1", "value1")
- c.Set("key2", "value2")
-
- c.Clear()
- collector.AssertNumberOfCalls(t, "RecordEviction", 2)
-
- _, found1 := c.Get("key1")
- _, found2 := c.Get("key2")
- assert.False(t, found1, "Expected not to find key1 after clear")
- assert.False(t, found2, "Expected not to find key2 after clear")
-}
-
-func TestCacheCount(t *testing.T) {
- c, collector := setupCache[string](t, true)
-
- collector.On("RecordEviction", "test_cache").Times(3)
-
- c.Set("key1", "value1")
- c.Set("key2", "value2")
- c.Set("key3", "value3")
-
- assert.Equal(t, 3, c.Count(), "Expected count to be 3")
-
- c.Delete("key2")
- assert.Equal(t, 2, c.Count(), "Expected count to be 2 after deletion")
- collector.AssertNumberOfCalls(t, "RecordEviction", 1)
-
- c.Clear()
- assert.Equal(t, 0, c.Count(), "Expected count to be 0 after clear")
- collector.AssertNumberOfCalls(t, "RecordEviction", 3)
-}
-
-func TestCacheKeys(t *testing.T) {
- c, collector := setupCache[string](t, true)
-
- collector.On("RecordEviction", "test_cache").Times(3)
-
- c.Set("key1", "value1")
- c.Set("key2", "value2")
- c.Set("key3", "value3")
-
- keys := c.Keys()
- assert.Len(t, keys, 3, "Expected 3 keys")
- assert.ElementsMatch(t, []string{"key1", "key2", "key3"}, keys, "Keys do not match expected values")
-
- c.Delete("key2")
- keys = c.Keys()
- assert.Len(t, keys, 2, "Expected 2 keys after deletion")
- assert.ElementsMatch(t, []string{"key1", "key3"}, keys, "Keys do not match expected values after deletion")
- collector.AssertNumberOfCalls(t, "RecordEviction", 1)
-
- c.Clear()
- keys = c.Keys()
- assert.Len(t, keys, 0, "Expected no keys after clear")
- collector.AssertNumberOfCalls(t, "RecordEviction", 3)
-}
-
-func TestCacheValues(t *testing.T) {
- c, collector := setupCache[string](t, true)
-
- collector.On("RecordEviction", "test_cache").Times(3)
-
- c.Set("key1", "value1")
- c.Set("key2", "value2")
- c.Set("key3", "value3")
-
- values := c.Values()
- assert.Len(t, values, 3, "Expected 3 values")
- assert.ElementsMatch(t, []string{"value1", "value2", "value3"}, values, "Values do not match expected values")
-
- c.Delete("key2")
- values = c.Values()
- assert.Len(t, values, 2, "Expected 2 values after deletion")
- assert.ElementsMatch(t, []string{"value1", "value3"}, values, "Values do not match expected values after deletion")
- collector.AssertNumberOfCalls(t, "RecordEviction", 1)
-
- c.Clear()
- values = c.Values()
- assert.Len(t, values, 0, "Expected no values after clear")
- collector.AssertNumberOfCalls(t, "RecordEviction", 3)
-}
-
-func TestCacheContents(t *testing.T) {
- c, collector := setupCache[string](t, true)
-
- collector.On("RecordEviction", "test_cache").Times(3)
-
- c.Set("key1", "value1")
- c.Set("key2", "value2")
- c.Set("key3", "value3")
-
- contents := c.Contents()
- assert.Contains(t, contents, "key1", "Contents should contain key1")
- assert.Contains(t, contents, "key2", "Contents should contain key2")
- assert.Contains(t, contents, "key3", "Contents should contain key3")
-
- c.Delete("key2")
- contents = c.Contents()
- assert.Contains(t, contents, "key1", "Contents should contain key1")
- assert.NotContains(t, contents, "key2", "Contents should not contain key2")
- assert.Contains(t, contents, "key3", "Contents should contain key3")
- collector.AssertNumberOfCalls(t, "RecordEviction", 1)
-
- c.Clear()
- contents = c.Contents()
- assert.Equal(t, "[]", contents, "Contents should be empty after clear")
- collector.AssertNumberOfCalls(t, "RecordEviction", 3)
-}
diff --git a/pkg/cache/metrics.go b/pkg/cache/metrics.go
deleted file mode 100644
index 449b36c00373..000000000000
--- a/pkg/cache/metrics.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package cache
-
-import (
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promauto"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
-)
-
-// BaseMetricsCollector defines the interface for recording cache metrics.
-// Each method corresponds to a specific cache-related operation.
-type BaseMetricsCollector interface {
- RecordHit(cacheName string)
- RecordMiss(cacheName string)
- RecordSet(cacheName string)
- RecordDelete(cacheName string)
- RecordClear(cacheName string)
-}
-
-// EvictionMetricsCollector defines the interface for recording cache-specific eviction metrics.
-type EvictionMetricsCollector interface {
- RecordEviction(cacheName string)
-}
-
-// baseCollector encapsulates all Prometheus metrics with labels.
-// It holds Prometheus counters for cache operations, which help track
-// the performance and usage of the cache.
-type baseCollector struct {
- // Base metrics.
- hits *prometheus.CounterVec
- misses *prometheus.CounterVec
- sets *prometheus.CounterVec
- deletes *prometheus.CounterVec
- clears *prometheus.CounterVec
-}
-
-func init() {
- // Initialize the singleton baseCollector.
- // Set up Prometheus counters for cache operations (hits, misses, sets, deletes, clears).
- baseMetricsInstance = &baseCollector{
- hits: promauto.NewCounterVec(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "hits_total",
- Help: "Total number of cache hits.",
- }, []string{"cache_name"}),
-
- misses: promauto.NewCounterVec(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "misses_total",
- Help: "Total number of cache misses.",
- }, []string{"cache_name"}),
-
- sets: promauto.NewCounterVec(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "sets_total",
- Help: "Total number of cache set operations.",
- }, []string{"cache_name"}),
-
- deletes: promauto.NewCounterVec(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "deletes_total",
- Help: "Total number of cache delete operations.",
- }, []string{"cache_name"}),
-
- clears: promauto.NewCounterVec(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "clears_total",
- Help: "Total number of cache clear operations.",
- }, []string{"cache_name"}),
- }
-
- // Initialize the singleton evictionMetrics.
- // Set up Prometheus counters for cache evictions.
- evictionMetricsInstance = &evictionMetrics{
- evictions: promauto.NewCounterVec(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "evictions_total",
- Help: "Total number of cache evictions.",
- }, []string{"cache_name"}),
- }
-}
-
-var (
- baseMetricsInstance *baseCollector
- evictionMetricsInstance *evictionMetrics
-)
-
-// GetBaseMetricsCollector returns the singleton baseCollector instance.
-func GetBaseMetricsCollector() BaseMetricsCollector { return baseMetricsInstance }
-
-// GetEvictionMetricsCollector returns the singleton evictionMetrics instance.
-func GetEvictionMetricsCollector() EvictionMetricsCollector { return evictionMetricsInstance }
-
-// Implement BaseMetricsCollector interface methods.
-
-// RecordHit increments the counter for cache hits, tracking how often cache lookups succeed.
-func (m *baseCollector) RecordHit(cacheName string) { m.hits.WithLabelValues(cacheName).Inc() }
-
-// RecordMiss increments the counter for cache misses, tracking how often cache lookups fail.
-func (m *baseCollector) RecordMiss(cacheName string) { m.misses.WithLabelValues(cacheName).Inc() }
-
-// RecordSet increments the counter for cache set operations, tracking how often items are added/updated.
-func (m *baseCollector) RecordSet(cacheName string) { m.sets.WithLabelValues(cacheName).Inc() }
-
-// RecordDelete increments the counter for cache delete operations, tracking how often items are removed.
-func (m *baseCollector) RecordDelete(cacheName string) { m.deletes.WithLabelValues(cacheName).Inc() }
-
-// RecordClear increments the counter for cache clear operations, tracking how often the cache is completely cleared.
-func (m *baseCollector) RecordClear(cacheName string) { m.clears.WithLabelValues(cacheName).Inc() }
-
-// evictionMetrics implements EvictionMetricsCollector interface.
-type evictionMetrics struct {
- evictions *prometheus.CounterVec
-}
-
-// Implement EvictionMetricsCollector interface method.
-
-func (em *evictionMetrics) RecordEviction(cacheName string) {
- em.evictions.WithLabelValues(cacheName).Inc()
-}
diff --git a/pkg/cache/simple/simple.go b/pkg/cache/simple/simple.go
deleted file mode 100644
index daa269aa8afa..000000000000
--- a/pkg/cache/simple/simple.go
+++ /dev/null
@@ -1,141 +0,0 @@
-package simple
-
-import (
- "strings"
- "time"
-
- "github.com/patrickmn/go-cache"
-)
-
-const (
- defaultExpirationInterval = 12 * time.Hour
- defaultPurgeInterval = 13 * time.Hour
- defaultExpiration = cache.DefaultExpiration
-)
-
-// Cache wraps the go-cache library to provide an in-memory key-value store.
-type Cache[T any] struct {
- c *cache.Cache
- expiration time.Duration
- purgeInterval time.Duration
-}
-
-// CacheOption defines a function type used for configuring a Cache.
-type CacheOption[T any] func(*Cache[T])
-
-// 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[T any](interval time.Duration) CacheOption[T] {
- return func(c *Cache[T]) { 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[T any](interval time.Duration) CacheOption[T] {
- return func(c *Cache[T]) { c.purgeInterval = interval }
-}
-
-// NewCache 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 NewCache[T any](opts ...CacheOption[T]) *Cache[T] {
- return NewCacheWithData[T](nil, opts...)
-}
-
-// CacheEntry represents a single entry in the cache, consisting of a key and its corresponding value.
-type CacheEntry[T any] struct {
- // Key is the unique identifier for the entry.
- Key string
- // Value is the data stored in the entry.
- Value T
-}
-
-// NewCacheWithData constructs a new in-memory cache with existing data.
-// It also accepts CacheOption parameters to override default configuration values.
-func NewCacheWithData[T any](data []CacheEntry[T], opts ...CacheOption[T]) *Cache[T] {
- instance := &Cache[T]{expiration: defaultExpirationInterval, purgeInterval: defaultPurgeInterval}
- for _, opt := range opts {
- opt(instance)
- }
-
- // 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[T]) Set(key string, value T) {
- c.c.Set(key, value, defaultExpiration)
-}
-
-// Get returns the value for the given key.
-func (c *Cache[T]) Get(key string) (T, bool) {
- var value T
-
- v, ok := c.c.Get(key)
- if !ok {
- return value, false
- }
-
- value, ok = v.(T)
- return value, ok
-}
-
-// Exists returns true if the given key exists in the cache.
-func (c *Cache[T]) Exists(key string) bool {
- _, ok := c.c.Get(key)
- return ok
-}
-
-// Delete removes the key-value pair from the cache.
-func (c *Cache[T]) Delete(key string) {
- c.c.Delete(key)
-}
-
-// Clear removes all key-value pairs from the cache.
-func (c *Cache[T]) Clear() {
- c.c.Flush()
-}
-
-// Count returns the number of key-value pairs in the cache.
-func (c *Cache[T]) Count() int {
- return c.c.ItemCount()
-}
-
-// Keys returns all keys in the cache.
-func (c *Cache[T]) Keys() []string {
- items := c.c.Items()
- res := make([]string, 0, len(items))
- for k := range items {
- res = append(res, k)
- }
- return res
-}
-
-// Values returns all values in the cache.
-func (c *Cache[T]) Values() []T {
- items := c.c.Items()
- res := make([]T, 0, len(items))
- for _, v := range items {
- obj, ok := v.Object.(T)
- if ok {
- res = append(res, obj)
- }
- }
- return res
-}
-
-// Contents returns a comma-separated string containing all keys in the cache.
-func (c *Cache[T]) Contents() string {
- items := c.c.Items()
- res := make([]string, 0, len(items))
- for k := range items {
- res = append(res, k)
- }
- return strings.Join(res, ",")
-}
diff --git a/pkg/cache/simple/simple_test.go b/pkg/cache/simple/simple_test.go
deleted file mode 100644
index aeee7cba462c..000000000000
--- a/pkg/cache/simple/simple_test.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package simple
-
-import (
- "fmt"
- "sort"
- "strings"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-)
-
-func TestCache(t *testing.T) {
- c := NewCache[string]()
-
- // Test set and get.
- c.Set("key1", "key1")
- v, ok := c.Get("key1")
- if !ok || v != "key1" {
- t.Fatalf("Unexpected value for key1: %v, %v", v, ok)
- }
-
- // Test exists.
- if !c.Exists("key1") {
- t.Fatalf("Expected key1 to exist")
- }
-
- // Test the count.
- if c.Count() != 1 {
- t.Fatalf("Unexpected count: %d", c.Count())
- }
-
- // Test delete.
- c.Delete("key1")
- v, ok = c.Get("key1")
- if ok || v != "" {
- t.Fatalf("Unexpected value for key1 after delete: %v, %v", v, ok)
- }
-
- // Test clear.
- c.Set("key10", "key10")
- c.Clear()
- v, ok = c.Get("key10")
- if ok || v != "" {
- t.Fatalf("Unexpected value for key10 after clear: %v, %v", v, ok)
- }
-
- // Test getting only the keys.
- keys := []string{"key1", "key2", "key3"}
- values := []string{"value1", "value2", "value3"}
- for i, k := range keys {
- c.Set(k, values[i])
- }
- k := c.Keys()
- sort.Strings(keys)
- sort.Strings(k)
- if !cmp.Equal(keys, k) {
- t.Fatalf("Unexpected keys: %v", k)
- }
-
- // Test getting only the values.
- vals := make([]string, 0, c.Count())
- vals = append(vals, c.Values()...)
- sort.Strings(vals)
- sort.Strings(values)
- if !cmp.Equal(values, vals) {
- t.Fatalf("Unexpected values: %v", vals)
- }
-
- // Test contents.
- items := c.Contents()
- sort.Strings(keys)
- res := strings.Split(items, ",")
- sort.Strings(res)
-
- if len(keys) != len(res) {
- t.Fatalf("Unexpected length of items: %d", len(res))
- }
- if !cmp.Equal(keys, res) {
- t.Fatalf("Unexpected items: %v", res)
- }
-}
-
-func TestCache_NewWithData(t *testing.T) {
- data := []CacheEntry[string]{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}}
- c := NewCacheWithData(data)
-
- // Test the count.
- if c.Count() != 3 {
- t.Fatalf("Unexpected count: %d", c.Count())
- }
-
- // Test contents.
- keys := []string{"key1", "key2", "key3"}
- items := c.Contents()
- sort.Strings(keys)
- res := strings.Split(items, ",")
- sort.Strings(res)
-
- if len(keys) != len(res) {
- t.Fatalf("Unexpected length of items: %d", len(res))
- }
- if !cmp.Equal(keys, res) {
- t.Fatalf("Unexpected items: %v", res)
- }
-}
-
-func setupBenchmarks(b *testing.B) *Cache[string] {
- b.Helper()
-
- c := NewCache[string]()
-
- for i := 0; i < 500_000; i++ {
- key := fmt.Sprintf("key%d", i)
- c.Set(key, key)
- }
-
- return c
-}
-
-func BenchmarkSet(b *testing.B) {
- c := NewCache[string]()
-
- for i := 0; i < b.N; i++ {
- key := fmt.Sprintf("key%d", i)
- c.Set(key, key)
- }
-}
-
-func BenchmarkGet(b *testing.B) {
- c := setupBenchmarks(b)
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- key := fmt.Sprintf("key%d", i)
- c.Get(key)
- }
-}
-
-func BenchmarkDelete(b *testing.B) {
- c := setupBenchmarks(b)
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- key := fmt.Sprintf("key%d", i)
- c.Delete(key)
- }
-}
-
-func BenchmarkCount(b *testing.B) {
- c := setupBenchmarks(b)
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- c.Count()
- }
-}
-
-func BenchmarkContents(b *testing.B) {
- c := setupBenchmarks(b)
- b.ResetTimer()
-
- var s string
-
- for i := 0; i < b.N; i++ {
- s = c.Contents()
- }
-
- _ = s
-}
diff --git a/pkg/channelmetrics/metrics_collector/prometheus/collector.go b/pkg/channelmetrics/metrics_collector/prometheus/collector.go
deleted file mode 100644
index 72d8013a06c5..000000000000
--- a/pkg/channelmetrics/metrics_collector/prometheus/collector.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package prometheus
-
-import (
- "fmt"
- "sync"
- "time"
-
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promauto"
-)
-
-// MetricsCollector implements the |channelmetrics.MetricsCollector| interface using Prometheus.
-// It records various metrics related to channel operations.
-type MetricsCollector struct {
- produceDuration prometheus.Histogram
- consumeDuration prometheus.Histogram
- channelLen prometheus.Gauge
- channelCap prometheus.Gauge
-}
-
-var (
- collectors = make(map[string]*MetricsCollector)
- collectorsMu sync.Mutex
-)
-
-// NewMetricsCollector creates a new MetricsCollector with
-// histograms for produce and consume durations, and gauges for channel length and capacity.
-// It accepts namespace, subsystem, and chanName parameters to organize metrics.
-// The function initializes and returns a pointer to a MetricsCollector struct
-// that contains the following Prometheus metrics:
-//
-// - produceDuration: a Histogram metric that measures the duration of producing an item.
-// It tracks the time taken to add an item to the ObservableChan.
-// This metric helps to monitor the performance and latency of item production.
-//
-// - consumeDuration: a Histogram metric that measures the duration of consuming an item.
-// It tracks the time taken to retrieve an item from the ObservableChan.
-// This metric helps to monitor the performance and latency of item consumption.
-//
-// - channelLen: a Gauge metric that measures the current size of the channel buffer.
-// It tracks the number of items in the channel buffer at any given time.
-// This metric helps to monitor the utilization of the channel buffer.
-//
-// - channelCap: a Gauge metric that measures the capacity of the channel buffer.
-// It tracks the maximum number of items that the channel buffer can hold.
-// This metric helps to understand the configuration and potential limits of the channel buffer.
-//
-// These metrics are useful for monitoring the performance and throughput of the ObservableChan.
-// By tracking the durations of item production and consumption, as well as the buffer size and capacity,
-// you can identify bottlenecks, optimize performance, and ensure that the ObservableChan is operating efficiently.
-func NewMetricsCollector(chanName, namespace, subsystem string) *MetricsCollector {
- key := fmt.Sprintf("%s_%s_%s", namespace, subsystem, chanName)
-
- collectorsMu.Lock()
- defer collectorsMu.Unlock()
-
- if collector, exists := collectors[key]; exists {
- return collector
- }
-
- collector := &MetricsCollector{
- produceDuration: promauto.NewHistogram(prometheus.HistogramOpts{
- Name: metricName(chanName, "produce_duration_microseconds"),
- Namespace: namespace,
- Subsystem: subsystem,
- Help: "Duration of producing an item in microseconds.",
- Buckets: prometheus.ExponentialBuckets(1, 2, 20),
- }),
- consumeDuration: promauto.NewHistogram(prometheus.HistogramOpts{
- Name: metricName(chanName, "consume_duration_microseconds"),
- Namespace: namespace,
- Subsystem: subsystem,
- Help: "Duration of consuming an item in microseconds.",
- Buckets: prometheus.ExponentialBuckets(1, 2, 20),
- }),
- channelLen: promauto.NewGauge(prometheus.GaugeOpts{
- Name: metricName(chanName, "channel_length"),
- Namespace: namespace,
- Subsystem: subsystem,
- Help: "Current size of the channel buffer.",
- }),
- channelCap: promauto.NewGauge(prometheus.GaugeOpts{
- Name: metricName(chanName, "channel_capacity"),
- Namespace: namespace,
- Subsystem: subsystem,
- Help: "Capacity of the channel buffer.",
- }),
- }
-
- collectors[key] = collector
- return collector
-}
-
-// metricName constructs a full metric name by combining the channel name with the specific metric.
-func metricName(chanName, metric string) string { return chanName + "_" + metric }
-
-// RecordProduceDuration records the duration taken to produce an item into the channel.
-func (c *MetricsCollector) RecordProduceDuration(duration time.Duration) {
- c.produceDuration.Observe(float64(duration.Microseconds()))
-}
-
-// RecordConsumeDuration records the duration taken to consume an item from the channel.
-func (c *MetricsCollector) RecordConsumeDuration(duration time.Duration) {
- c.consumeDuration.Observe(float64(duration.Microseconds()))
-}
-
-// RecordChannelLen records the current size of the channel buffer.
-func (c *MetricsCollector) RecordChannelLen(size int) { c.channelLen.Set(float64(size)) }
-
-// RecordChannelCap records the capacity of the channel buffer.
-func (c *MetricsCollector) RecordChannelCap(capacity int) { c.channelCap.Set(float64(capacity)) }
diff --git a/pkg/channelmetrics/noopcollector.go b/pkg/channelmetrics/noopcollector.go
deleted file mode 100644
index 7bdd7f1193d2..000000000000
--- a/pkg/channelmetrics/noopcollector.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package channelmetrics
-
-import "time"
-
-// noopCollector is a default implementation of the MetricsCollector interface
-// for internal package use only.
-type noopCollector struct{}
-
-func (noopCollector) RecordProduceDuration(duration time.Duration) {}
-func (noopCollector) RecordConsumeDuration(duration time.Duration) {}
-func (noopCollector) RecordChannelLen(size int) {}
-func (noopCollector) RecordChannelCap(capacity int) {}
diff --git a/pkg/channelmetrics/observablechan.go b/pkg/channelmetrics/observablechan.go
deleted file mode 100644
index 09683faa5817..000000000000
--- a/pkg/channelmetrics/observablechan.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// Package channelmetrics provides a flexible way to wrap Go channels with
-// additional metrics collection capabilities. This allows for monitoring
-// and tracking of channel usage and performance using different metrics backends.
-package channelmetrics
-
-import (
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-// MetricsCollector is an interface for collecting metrics. Implementations
-// of this interface can be used to record various channel metrics.
-type MetricsCollector interface {
- RecordProduceDuration(duration time.Duration)
- RecordConsumeDuration(duration time.Duration)
- RecordChannelLen(size int)
- RecordChannelCap(capacity int)
-}
-
-// ObservableChan wraps a Go channel and collects metrics about its usage.
-// It supports any type of channel and records metrics using a provided
-// MetricsCollector implementation.
-type ObservableChan[T any] struct {
- ch chan T
- metrics MetricsCollector
-}
-
-// NewObservableChan creates a new ObservableChan wrapping the provided channel.
-// It records the channel's capacity immediately and sets up metrics collection
-// using the provided MetricsCollector and channel name. The chanName is used to
-// distinguish between metrics for different channels by incorporating it into
-// the metric names.
-func NewObservableChan[T any](ch chan T, metrics MetricsCollector) *ObservableChan[T] {
- if metrics == nil {
- metrics = noopCollector{}
- }
- oChan := &ObservableChan[T]{
- ch: ch,
- metrics: metrics,
- }
- oChan.RecordChannelCapacity()
- // Record the current length of the channel.
- // Note: The channel is likely empty, but it may contain items if it
- // was pre-existing.
- oChan.RecordChannelLen()
- return oChan
-}
-
-// Close closes the channel and records the current size of the channel buffer.
-func (oc *ObservableChan[T]) Close() {
- close(oc.ch)
- oc.RecordChannelLen()
-}
-
-// Send sends an item into the channel and records the duration taken to do so.
-// It also updates the current size of the channel buffer. This method blocks
-// until the item is sent.
-func (oc *ObservableChan[T]) Send(item T) { _ = oc.SendCtx(context.Background(), item) }
-
-// SendCtx sends an item into the channel with context and records the duration
-// taken to do so. It also updates the current size of the channel buffer and
-// supports context cancellation.
-func (oc *ObservableChan[T]) SendCtx(ctx context.Context, item T) error {
- defer func(start time.Time) {
- oc.metrics.RecordProduceDuration(time.Since(start))
- oc.RecordChannelLen()
- }(time.Now())
-
- return common.CancellableWrite(ctx, oc.ch, item)
-}
-
-// Recv receives an item from the channel and records the duration taken to do
-// so. It also updates the current size of the channel buffer. This method
-// blocks until an item is available.
-func (oc *ObservableChan[T]) Recv() T {
- v, _ := oc.RecvCtx(context.Background())
- return v
-}
-
-// RecvCtx receives an item from the channel with context and records the
-// duration taken to do so. It also updates the current size of the channel
-// buffer and supports context cancellation. If an error occurs, it logs the
-// error.
-func (oc *ObservableChan[T]) RecvCtx(ctx context.Context) (T, error) {
- defer func(start time.Time) {
- oc.metrics.RecordConsumeDuration(time.Since(start))
- oc.RecordChannelLen()
- }(time.Now())
-
- return common.CancellableRead(ctx, oc.ch)
-}
-
-// RecordChannelCapacity records the capacity of the channel buffer.
-func (oc *ObservableChan[T]) RecordChannelCapacity() { oc.metrics.RecordChannelCap(cap(oc.ch)) }
-
-// RecordChannelLen records the current size of the channel buffer.
-func (oc *ObservableChan[T]) RecordChannelLen() { oc.metrics.RecordChannelLen(len(oc.ch)) }
diff --git a/pkg/channelmetrics/observablechan_test.go b/pkg/channelmetrics/observablechan_test.go
deleted file mode 100644
index 8e397389169e..000000000000
--- a/pkg/channelmetrics/observablechan_test.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package channelmetrics
-
-import (
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-type MockMetricsCollector struct{ mock.Mock }
-
-func (m *MockMetricsCollector) RecordProduceDuration(duration time.Duration) { m.Called(duration) }
-
-func (m *MockMetricsCollector) RecordConsumeDuration(duration time.Duration) { m.Called(duration) }
-
-func (m *MockMetricsCollector) RecordChannelLen(size int) { m.Called(size) }
-
-func (m *MockMetricsCollector) RecordChannelCap(capacity int) { m.Called(capacity) }
-
-func TestObservableChanSend(t *testing.T) {
- t.Parallel()
-
- mockMetrics := new(MockMetricsCollector)
- bufferCap := 10
-
- mockMetrics.On("RecordProduceDuration", mock.Anything).Once()
- mockMetrics.On("RecordChannelLen", mock.AnythingOfType("int")).Twice()
- mockMetrics.On("RecordChannelCap", bufferCap).Once()
-
- ch := make(chan int, bufferCap)
- oc := NewObservableChan(ch, mockMetrics)
- assert.Equal(t, bufferCap, cap(oc.ch))
-
- err := oc.SendCtx(context.Background(), 1)
- assert.NoError(t, err)
-
- mockMetrics.AssertExpectations(t)
-}
-
-func TestObservableChanRecv(t *testing.T) {
- t.Parallel()
-
- mockMetrics := new(MockMetricsCollector)
- bufferCap := 10
-
- mockMetrics.On("RecordConsumeDuration", mock.Anything).Once() // For the send
- mockMetrics.On("RecordProduceDuration", mock.Anything).Once()
- mockMetrics.On("RecordChannelLen", mock.AnythingOfType("int")).Times(3) // For the send and recv
- mockMetrics.On("RecordChannelCap", bufferCap).Once()
-
- ch := make(chan int, bufferCap)
- oc := NewObservableChan(ch, mockMetrics)
- assert.Equal(t, bufferCap, cap(oc.ch))
-
- go func() {
- err := oc.SendCtx(context.Background(), 1)
- assert.NoError(t, err)
- }()
-
- time.Sleep(100 * time.Millisecond) // Ensure Send happens before Recv
-
- _, err := oc.RecvCtx(context.Background())
- assert.NoError(t, err)
-
- mockMetrics.AssertExpectations(t)
-}
-
-func TestObservableChanRecordChannelCapacity(t *testing.T) {
- t.Parallel()
-
- mockMetrics := new(MockMetricsCollector)
- bufferCap := 10
-
- mockMetrics.On("RecordChannelCap", bufferCap).Twice()
- mockMetrics.On("RecordChannelLen", mock.AnythingOfType("int")).Once()
-
- ch := make(chan int, bufferCap)
- oc := NewObservableChan(ch, mockMetrics)
-
- oc.RecordChannelCapacity()
-
- mockMetrics.AssertExpectations(t)
-}
-
-func TestObservableChanRecordChannelLen(t *testing.T) {
- t.Parallel()
-
- mockMetrics := new(MockMetricsCollector)
- bufferCap := 10
-
- mockMetrics.On("RecordChannelLen", mock.AnythingOfType("int")).Twice()
- mockMetrics.On("RecordChannelCap", bufferCap).Once()
-
- ch := make(chan int, bufferCap)
- oc := NewObservableChan(ch, mockMetrics)
-
- oc.RecordChannelLen()
-
- mockMetrics.AssertExpectations(t)
-}
-
-func TestObservableChan_Close(t *testing.T) {
- t.Parallel()
-
- mockMetrics := new(MockMetricsCollector)
- bufferCap := 1
-
- mockMetrics.On("RecordChannelCap", bufferCap).Once()
- mockMetrics.On("RecordChannelLen", mock.AnythingOfType("int")).Twice()
-
- ch := make(chan int, bufferCap)
- oc := NewObservableChan(ch, mockMetrics)
-
- oc.Close()
-
- mockMetrics.AssertExpectations(t)
-}
-
-func TestObservableChanClosed(t *testing.T) {
- t.Parallel()
-
- ch := make(chan int)
- close(ch)
- oc := NewObservableChan(ch, nil)
-
- ctx, cancel := context.WithCancel(context.Background())
- // Closed channel should return with an error.
- v, err := oc.RecvCtx(ctx)
- assert.Error(t, err)
- assert.Equal(t, 0, v)
-
- // Cancelled context should also return with an error.
- cancel()
- _, err = oc.RecvCtx(ctx)
- assert.Error(t, err)
-}
diff --git a/pkg/cleantemp/cleantemp.go b/pkg/cleantemp/cleantemp.go
deleted file mode 100644
index 8a9d39e48219..000000000000
--- a/pkg/cleantemp/cleantemp.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package cleantemp
-
-import (
- "fmt"
- "io"
- "os"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
-
- "github.com/mitchellh/go-ps"
-
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-const (
- defaultExecPath = "trufflehog"
- defaultArtifactPrefixFormat = "%s-%d-"
-)
-
-// MkdirTemp returns a temporary directory path formatted as:
-// trufflehog--
-func MkdirTemp() (string, error) {
- pid := os.Getpid()
- tmpdir := fmt.Sprintf(defaultArtifactPrefixFormat, defaultExecPath, pid)
- dir, err := os.MkdirTemp(os.TempDir(), tmpdir)
- if err != nil {
- return "", err
- }
- return dir, nil
-}
-
-// Unlike MkdirTemp, we only want to generate the filename string.
-// The tempfile creation in trufflehog we're interested in
-// is generally handled by "github.com/trufflesecurity/disk-buffer-reader"
-func MkFilename() string {
- pid := os.Getpid()
- filename := fmt.Sprintf(defaultArtifactPrefixFormat, defaultExecPath, pid)
- return filename
-}
-
-// Only compile during startup.
-var trufflehogRE = regexp.MustCompile(`^trufflehog-\d+-\d+$`)
-
-// CleanTempArtifacts deletes orphaned temp directories and files that do not contain running PID values.
-func CleanTempArtifacts(ctx logContext.Context) error {
- executablePath, err := os.Executable()
- if err != nil {
- executablePath = defaultExecPath
- }
- execName := filepath.Base(executablePath)
-
- var pids []string
- procs, err := ps.Processes()
- if err != nil {
- return fmt.Errorf("error getting jobs PIDs: %w", err)
- }
-
- for _, proc := range procs {
- if proc.Executable() == execName {
- pids = append(pids, strconv.Itoa(proc.Pid()))
- }
- }
-
- if len(pids) == 0 {
- ctx.Logger().V(5).Info("No trufflehog processes were found")
- return nil
- }
-
- 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(entry.Name(), fmt.Sprintf("-%s-", pidval)) {
- shouldDelete = false
- break
- }
- }
-
- if shouldDelete {
- path := filepath.Join(tempDir, entry.Name())
- isDir := entry.IsDir()
- if isDir {
- err = os.RemoveAll(path)
- } else {
- err = os.Remove(path)
- }
- if err != nil {
- return fmt.Errorf("error deleting temp artifact (dir: %v) %s: %w", isDir, path, err)
- }
-
- ctx.Logger().V(4).Info("Deleted orphaned temp artifact", "artifact", path)
- }
- }
- }
-
- return nil
-}
-
-// CleanTempDirsForLegacyJSON removes all directories that start with "trufflehog-"
-// from either the provided clonePath (if not empty) or the OS temp directory.
-func CleanTempDirsForLegacyJSON(baseDir string) error {
- // If no custom clone path was provided, clean repos from the OS temp directory
- // since that's where they were cloned during the scan.
- if baseDir == "" {
- baseDir = os.TempDir()
- }
-
- entries, err := os.ReadDir(baseDir)
- if err != nil {
- return err
- }
-
- for _, entry := range entries {
- if entry.IsDir() && strings.HasPrefix(entry.Name(), "trufflehog-") {
- fullPath := filepath.Join(baseDir, entry.Name())
- if err := os.RemoveAll(fullPath); err != nil {
- return err
- }
- }
- }
-
- return nil
-}
diff --git a/pkg/cleantemp/cleantemp_test.go b/pkg/cleantemp/cleantemp_test.go
deleted file mode 100644
index 2d3a9cbbd2e3..000000000000
--- a/pkg/cleantemp/cleantemp_test.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package cleantemp
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- "github.com/mitchellh/go-ps"
- "github.com/stretchr/testify/assert"
-)
-
-func TestExecName(t *testing.T) {
- executablePath, err := os.Executable()
- assert.Nil(t, err)
- execName := filepath.Base(executablePath)
- assert.Equal(t, "cleantemp.test", execName)
-
- procs, err := ps.Processes()
- assert.Nil(t, err)
- assert.NotEmpty(t, procs)
-
- found := false
- for _, proc := range procs {
- if proc.Executable() == execName {
- found = true
- }
- }
-
- assert.True(t, found)
-}
-
-func TestCleanTempDirsForLegacyJSON(t *testing.T) {
- baseDir := t.TempDir()
-
- // Create dirs that should be deleted
- dir1 := filepath.Join(baseDir, "trufflehog-123")
- dir2 := filepath.Join(baseDir, "trufflehog-456")
- assert.NoError(t, os.Mkdir(dir1, 0o755))
- assert.NoError(t, os.Mkdir(dir2, 0o755))
-
- // Create dirs that should NOT be deleted
- keepDir := filepath.Join(baseDir, "keepme-123")
- assert.NoError(t, os.Mkdir(keepDir, 0o755))
-
- // Create a file with trufflehog- prefix (should not be deleted because only dirs are deleted)
- keepFile := filepath.Join(baseDir, "trufflehog-file")
- assert.NoError(t, os.WriteFile(keepFile, []byte("data"), 0o644))
-
- err := CleanTempDirsForLegacyJSON(baseDir)
- assert.NoError(t, err)
-
- _, err = os.Stat(dir1)
- assert.True(t, os.IsNotExist(err))
- _, err = os.Stat(dir2)
- assert.True(t, os.IsNotExist(err))
-
- _, err = os.Stat(keepDir)
- assert.NoError(t, err)
-
- _, err = os.Stat(keepFile)
- assert.NoError(t, err)
-}
diff --git a/pkg/common/context.go b/pkg/common/context.go
deleted file mode 100644
index 1602f1b7a16f..000000000000
--- a/pkg/common/context.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package common
-
-import "context"
-
-// ChannelClosedErr indicates that a read was performed from a closed channel.
-type ChannelClosedErr struct{}
-
-func (ChannelClosedErr) Error() string { return "channel is closed" }
-
-func IsDone(ctx context.Context) bool {
- select {
- case <-ctx.Done():
- return true
- default:
- return false
- }
-}
-
-// CancellableWrite blocks on writing the item to the channel but can be
-// cancelled by the context. If both the context is cancelled and the channel
-// write would succeed, either operation will be performed randomly, however
-// priority is given to context cancellation.
-func CancellableWrite[T any](ctx context.Context, ch chan<- T, item T) error {
- select {
- case <-ctx.Done(): // priority to context cancellation
- return ctx.Err()
- default:
- select {
- case <-ctx.Done():
- return ctx.Err()
- case ch <- item:
- return nil
- }
- }
-}
-
-// CancellableRead blocks on receiving an item from the channel but can be
-// cancelled by the context. If the channel is closed, a ChannelClosedErr is
-// returned. If both the context is cancelled and the channel read would
-// succeed, either operation will be performed randomly, however priority is
-// given to context cancellation.
-func CancellableRead[T any](ctx context.Context, ch <-chan T) (T, error) {
- var zero T // zero value of type T
-
- select {
- case <-ctx.Done(): // priority to context cancellation
- return zero, ctx.Err()
- default:
- select {
- case <-ctx.Done():
- return zero, ctx.Err()
- case item, ok := <-ch:
- if !ok {
- return item, ChannelClosedErr{}
- }
- return item, nil
- }
- }
-}
diff --git a/pkg/common/export_error.go b/pkg/common/export_error.go
deleted file mode 100644
index 68baab3e5604..000000000000
--- a/pkg/common/export_error.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package common
-
-// ExportError is an implementation of error that can be JSON marshalled. It
-// must be a public exported type for this reason.
-type ExportError string
-
-func (e ExportError) Error() string { return string(e) }
-
-// ExportErrors converts a list of errors into []ExportError.
-func ExportErrors(errs ...error) []error {
- output := make([]error, 0, len(errs))
- for _, err := range errs {
- output = append(output, ExportError(err.Error()))
- }
- return output
-}
diff --git a/pkg/common/filter.go b/pkg/common/filter.go
deleted file mode 100644
index 8462ccac05fb..000000000000
--- a/pkg/common/filter.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package common
-
-import (
- "bufio"
- "fmt"
- "os"
- "regexp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-type Filter struct {
- include *FilterRuleSet
- exclude *FilterRuleSet
-}
-
-type FilterRuleSet []regexp.Regexp
-
-// FilterEmpty returns a Filter that always passes.
-func FilterEmpty() *Filter {
- filter, err := FilterFromFiles("", "")
- if err != nil {
- context.Background().Logger().Error(err, "could not create empty filter")
- os.Exit(1)
- }
- return filter
-}
-
-// FilterFromFiles creates a Filter using the rules in the provided include and exclude files.
-func FilterFromFiles(includeFilterPath, excludeFilterPath string) (*Filter, error) {
- includeRules, err := FilterRulesFromFile(includeFilterPath)
- if err != nil {
- return nil, fmt.Errorf("could not create include rules: %s", err)
- }
- excludeRules, err := FilterRulesFromFile(excludeFilterPath)
- if err != nil {
- return nil, fmt.Errorf("could not create exclude rules: %s", err)
- }
-
- // If no includeFilterPath is provided, every pattern should pass the include rules.
- if includeFilterPath == "" {
- includeRules = &FilterRuleSet{*regexp.MustCompile("")}
- }
-
- filter := &Filter{
- include: includeRules,
- exclude: excludeRules,
- }
-
- return filter, nil
-}
-
-// FilterRulesFromFile loads the list of regular expression filter rules in `source` and creates a FilterRuleSet.
-func FilterRulesFromFile(source string) (*FilterRuleSet, error) {
- rules := FilterRuleSet{}
- if source == "" {
- return &rules, nil
- }
-
- commentPattern := regexp.MustCompile(`^\s*#`)
- emptyLinePattern := regexp.MustCompile(`^\s*$`)
-
- file, err := os.Open(source)
- logger := context.Background().Logger().WithValues("file", source)
- if err != nil {
- logger.Error(err, "unable to open filter file", "file", source)
- os.Exit(1)
- }
- defer func(file *os.File) {
- err := file.Close()
- if err != nil {
- logger.Error(err, "unable to close filter file")
- os.Exit(1)
- }
- }(file)
-
- scanner := bufio.NewScanner(file)
- for scanner.Scan() {
- line := scanner.Text()
- if commentPattern.MatchString(line) {
- continue
- }
- if emptyLinePattern.MatchString(line) {
- continue
- }
- pattern, err := regexp.Compile(line)
- if err != nil {
- return nil, fmt.Errorf("can not compile regular expression: %s", line)
- }
- rules = append(rules, *pattern)
- }
- return &rules, nil
-}
-
-// Pass returns true if the include FilterRuleSet matches the pattern and the exclude FilterRuleSet does not match.
-func (filter *Filter) Pass(object string) bool {
- if filter == nil {
- return true
- }
-
- excluded := filter.exclude.Matches(object)
- included := filter.include.Matches(object)
-
- return !excluded && included
-}
-
-// Matches will return true if any of the regular expressions in the FilterRuleSet match the pattern.
-func (rules *FilterRuleSet) Matches(object string) bool {
- if rules == nil {
- return false
- }
- for _, rule := range *rules {
- if rule.MatchString(object) {
- return true
- }
- }
- return false
-}
-
-// ShouldExclude return true if any regular expressions in the exclude FilterRuleSet matches the path.
-func (filter *Filter) ShouldExclude(path string) bool {
- return filter.exclude.Matches(path)
-}
diff --git a/pkg/common/filter_test.go b/pkg/common/filter_test.go
deleted file mode 100644
index a4df8db62265..000000000000
--- a/pkg/common/filter_test.go
+++ /dev/null
@@ -1,178 +0,0 @@
-package common
-
-import (
- "os"
- "regexp"
- "testing"
-)
-
-func TestFilterBasic(t *testing.T) {
- type filterTest struct {
- filter Filter
- pattern string
- pass bool
- }
- tests := map[string]filterTest{
- "IncludePassed": {
- filter: Filter{
- include: &FilterRuleSet{*regexp.MustCompile("test")},
- },
- pattern: "teststring",
- pass: true,
- },
- "IncludeFiltered": {
- filter: Filter{
- include: &FilterRuleSet{*regexp.MustCompile("nomatch")},
- },
- pattern: "teststring",
- pass: false,
- },
- "ExcludePassed": {
- filter: Filter{
- include: &FilterRuleSet{*regexp.MustCompile("")},
- exclude: &FilterRuleSet{*regexp.MustCompile("nomatch")},
- },
- pattern: "teststring",
- pass: true,
- },
- "ExcludeFiltered": {
- filter: Filter{
- include: &FilterRuleSet{*regexp.MustCompile("")},
- exclude: &FilterRuleSet{*regexp.MustCompile("test")},
- },
- pattern: "teststring",
- pass: false,
- },
- "IncludeExcludeDifferentPass": {
- filter: Filter{
- include: &FilterRuleSet{*regexp.MustCompile("test")},
- exclude: &FilterRuleSet{*regexp.MustCompile("nomatch")},
- },
- pattern: "teststring",
- pass: true,
- },
- "IncludeExcludeDifferentFiltered": {
- filter: Filter{
- include: &FilterRuleSet{*regexp.MustCompile("nomatch")},
- exclude: &FilterRuleSet{*regexp.MustCompile("test")},
- },
- pattern: "teststring",
- pass: false,
- },
- "IncludeExcludeSameFiltered": {
- filter: Filter{
- include: &FilterRuleSet{*regexp.MustCompile("test")},
- exclude: &FilterRuleSet{*regexp.MustCompile("test")},
- },
- pattern: "teststring",
- pass: false,
- },
- }
-
- for name, test := range tests {
- if test.filter.Pass(test.pattern) != test.pass {
- t.Errorf("%s: unexpected filter result. pattern: %q, pass: %t", name, test.pattern, !test.pass)
- }
- }
-}
-
-func TestFilterFromFile(t *testing.T) {
- type filterTest struct {
- includeFile bool
- excludeFile bool
- includeFileContents string
- excludeFileContents string
- pattern string
- pass bool
- }
- tests := map[string]filterTest{
- "includeFileOnlyPass": {
- includeFile: true,
- excludeFile: false,
- includeFileContents: "test",
- pattern: "test",
- pass: true,
- },
- "includeFileOnlyFiltered": {
- includeFile: true,
- excludeFile: false,
- includeFileContents: "nomatch",
- pattern: "test",
- pass: false,
- },
- "includeFileEmptyFiltered": {
- includeFile: true,
- excludeFile: false,
- includeFileContents: "",
- pattern: "test",
- pass: false,
- },
- "excludeFileOnlyPass": {
- includeFile: false,
- excludeFile: true,
- excludeFileContents: "nomatch",
- pattern: "test",
- pass: true,
- },
- "excludeFileOnlyFiltered": {
- includeFile: false,
- excludeFile: true,
- excludeFileContents: "test",
- pattern: "test",
- pass: false,
- },
- "BothFilesEmptyExcludeFiltered": {
- includeFile: true,
- excludeFile: true,
- excludeFileContents: "",
- includeFileContents: "",
- pattern: "test",
- pass: false,
- },
- "EmptyLinesAreIgnored": {
- includeFile: false,
- excludeFile: true,
- excludeFileContents: " \ntest.txt",
- pattern: "hello world.txt",
- pass: true,
- },
- }
- for name, test := range tests {
- var includeTestFile, excludeTestFile string
- if test.includeFile {
- includeTestFile = "/tmp/trufflehog_test_ifilter.txt"
- if err := testFilterWriteFile(includeTestFile, []byte(test.includeFileContents)); err != nil {
- t.Fatalf("failed to create include rules file: %s", err)
- }
- defer os.Remove(includeTestFile)
- }
- if test.excludeFile {
- excludeTestFile = "/tmp/trufflehog_test_xfilter.txt"
- if err := testFilterWriteFile(excludeTestFile, []byte(test.excludeFileContents)); err != nil {
- t.Fatalf("failed to create include rules file: %s", err)
- }
- defer os.Remove(excludeTestFile)
- }
-
- filter, err := FilterFromFiles(includeTestFile, excludeTestFile)
- if err != nil {
- t.Errorf("failed to create filter from files: %s", err)
- }
-
- if filter.Pass(test.pattern) != test.pass {
- t.Errorf("%s: unexpected filter result. pattern: %q, pass: %t", name, test.pattern, !test.pass)
- }
- }
-}
-
-func testFilterWriteFile(filename string, content []byte) error {
- f, err := os.Create(filename)
- if err != nil {
- return err
- }
- _, err = f.Write(content)
- if err != nil {
- return err
- }
- return f.Close()
-}
diff --git a/pkg/common/glob/glob.go b/pkg/common/glob/glob.go
deleted file mode 100644
index ced57fffe988..000000000000
--- a/pkg/common/glob/glob.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package glob
-
-import (
- "fmt"
-
- "github.com/gobwas/glob"
-)
-
-// Filter is a generic filter for excluding and including globs (limited
-// regular expressions). Exclusion takes precedence if both include and exclude
-// lists are provided.
-type Filter struct {
- exclude []glob.Glob
- include []glob.Glob
-}
-
-type globFilterOpt func(*Filter) error
-
-// WithExcludeGlobs adds exclude globs to the filter.
-func WithExcludeGlobs(excludes ...string) globFilterOpt {
- return func(f *Filter) error {
- for _, exclude := range excludes {
- g, err := glob.Compile(exclude)
- if err != nil {
- return fmt.Errorf("invalid exclude glob %q: %w", exclude, err)
- }
- f.exclude = append(f.exclude, g)
- }
- return nil
- }
-}
-
-// WithIncludeGlobs adds include globs to the filter.
-func WithIncludeGlobs(includes ...string) globFilterOpt {
- return func(f *Filter) error {
- for _, include := range includes {
- g, err := glob.Compile(include)
- if err != nil {
- return fmt.Errorf("invalid include glob %q: %w", include, err)
- }
- f.include = append(f.include, g)
- }
- return nil
- }
-}
-
-// NewGlobFilter creates a new Filter with the provided options.
-func NewGlobFilter(opts ...globFilterOpt) (*Filter, error) {
- filter := &Filter{}
- for _, opt := range opts {
- if err := opt(filter); err != nil {
- return nil, err
- }
- }
- return filter, nil
-}
-
-// ShouldInclude returns whether the object is in the include list or not in
-// the exclude list (exclude taking precedence).
-func (f *Filter) ShouldInclude(object string) bool {
- if f == nil {
- return true
- }
- exclude, include := len(f.exclude), len(f.include)
- if exclude == 0 && include == 0 {
- return true
- } else if exclude > 0 && include == 0 {
- return f.shouldIncludeFromExclude(object)
- } else if exclude == 0 && include > 0 {
- return f.shouldIncludeFromInclude(object)
- } else {
- if ok, err := f.shouldIncludeFromBoth(object); err == nil {
- return ok
- }
- // Ambiguous case.
- return false
- }
-}
-
-// shouldIncludeFromExclude checks for explicitly excluded paths. This should
-// only be called when the include list is empty.
-func (f *Filter) shouldIncludeFromExclude(object string) bool {
- for _, g := range f.exclude {
- if g.Match(object) {
- return false
- }
- }
- return true
-}
-
-// shouldIncludeFromInclude checks for explicitly included paths. This should
-// only be called when the exclude list is empty.
-func (f *Filter) shouldIncludeFromInclude(object string) bool {
- for _, g := range f.include {
- if g.Match(object) {
- return true
- }
- }
- return false
-}
-
-// shouldIncludeFromBoth checks for either excluded or included paths. Exclusion
-// takes precedence. If neither list contains the object, true is returned.
-func (f *Filter) shouldIncludeFromBoth(object string) (bool, error) {
- // Exclude takes precedence. If we find the object in the exclude list,
- // we should not match.
- for _, g := range f.exclude {
- if g.Match(object) {
- return false, nil
- }
- }
- // If we find the object in the include list, we should match.
- for _, g := range f.include {
- if g.Match(object) {
- return true, nil
- }
- }
- // If we find it in neither, return an error to let the caller decide.
- return false, fmt.Errorf("ambiguous match")
-}
diff --git a/pkg/common/glob/glob_test.go b/pkg/common/glob/glob_test.go
deleted file mode 100644
index 3fd057fae0fa..000000000000
--- a/pkg/common/glob/glob_test.go
+++ /dev/null
@@ -1,138 +0,0 @@
-package glob
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "pgregory.net/rapid"
-)
-
-type globTest struct {
- input string
- shouldInclude bool
-}
-
-func testGlobs(t *testing.T, filter *Filter, tests ...globTest) {
- for _, tt := range tests {
- t.Run(tt.input, func(t *testing.T) {
- // Invert because mentally it's easier to say whether an
- // input should be included.
- assert.Equal(t, tt.shouldInclude, filter.ShouldInclude(tt.input))
- })
- }
-}
-
-func TestGlobFilterExclude(t *testing.T) {
- filter, err := NewGlobFilter(WithExcludeGlobs("foo", "bar*"))
- assert.NoError(t, err)
-
- testGlobs(t, filter,
- globTest{"foo", false},
- globTest{"bar", false},
- globTest{"bara", false},
- globTest{"barb", false},
- globTest{"barbosa", false},
- globTest{"foobar", true},
- globTest{"food", true},
- globTest{"anything else", true},
- )
-}
-
-func TestGlobFilterInclude(t *testing.T) {
- filter, err := NewGlobFilter(WithIncludeGlobs("foo", "bar*"))
- assert.NoError(t, err)
-
- testGlobs(t, filter,
- globTest{"foo", true},
- globTest{"bar", true},
- globTest{"bara", true},
- globTest{"barb", true},
- globTest{"barbosa", true},
- globTest{"foobar", false},
- globTest{"food", false},
- globTest{"anything else", false},
- )
-}
-
-func TestGlobFilterEmpty(t *testing.T) {
- filter, err := NewGlobFilter()
- assert.NoError(t, err)
-
- testGlobs(t, filter,
- globTest{"foo", true},
- globTest{"bar", true},
- globTest{"bara", true},
- globTest{"barb", true},
- globTest{"barbosa", true},
- globTest{"foobar", true},
- globTest{"food", true},
- globTest{"anything else", true},
- )
-}
-
-func TestGlobFilterExcludeInclude(t *testing.T) {
- filter, err := NewGlobFilter(WithExcludeGlobs("/foo/bar/**"), WithIncludeGlobs("/foo/**"))
- assert.NoError(t, err)
-
- testGlobs(t, filter,
- globTest{"/foo/a", true},
- globTest{"/foo/b", true},
- globTest{"/foo/c/d/e", true},
- globTest{"/foo/bar/a", false},
- globTest{"/foo/bar/b", false},
- globTest{"/foo/bar/c/d/e", false},
- globTest{"/any/other/path", false},
- )
-}
-
-func TestGlobFilterExcludePrecedence(t *testing.T) {
- filter, err := NewGlobFilter(WithExcludeGlobs("foo"), WithIncludeGlobs("foo*"))
- assert.NoError(t, err)
-
- testGlobs(t, filter,
- globTest{"foo", false},
- globTest{"foobar", true},
- )
-}
-
-func TestGlobErrorContainsGlob(t *testing.T) {
- invalidGlob := "[this is invalid because it doesn't close the capture group"
- _, err := NewGlobFilter(WithExcludeGlobs(invalidGlob))
- assert.Error(t, err)
- assert.Contains(t, err.Error(), invalidGlob)
-}
-
-// The filters in this test should be mutually exclusive because one includes
-// and the other excludes the same glob.
-func TestGlobInverse(t *testing.T) {
- for _, glob := range []string{
- "a",
- "a*",
- "a**",
- "*a",
- "**a",
- "*",
- } {
- include, err := NewGlobFilter(WithIncludeGlobs(glob))
- assert.NoError(t, err)
- exclude, err := NewGlobFilter(WithExcludeGlobs(glob))
- assert.NoError(t, err)
- rapid.Check(t, func(t *rapid.T) {
- input := rapid.String().Draw(t, "input")
- a, b := include.ShouldInclude(input), exclude.ShouldInclude(input)
- if a == b {
- t.Fatalf("Filter(Include(%q)) == Filter(Exclude(%q)) == %v for input %q", glob, glob, a, input)
- }
- })
- }
-}
-
-func TestGlobDefaultFilters(t *testing.T) {
- for _, filter := range []*Filter{nil, {}} {
- rapid.Check(t, func(t *rapid.T) {
- if !filter.ShouldInclude(rapid.String().Draw(t, "input")) {
- t.Fatalf("filter %#v did not include input", filter)
- }
- })
- }
-}
diff --git a/pkg/common/http.go b/pkg/common/http.go
deleted file mode 100644
index 330e8a86509b..000000000000
--- a/pkg/common/http.go
+++ /dev/null
@@ -1,275 +0,0 @@
-package common
-
-import (
- "crypto/tls"
- "crypto/x509"
- "io"
- "net"
- "net/http"
- "strings"
- "time"
-
- "github.com/hashicorp/go-retryablehttp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/feature"
-)
-
-var caCerts = []string{
- // CN = ISRG Root X1
- // TODO: Expires Monday, June 4, 2035 at 4:04:38 AM Pacific
- `
------BEGIN CERTIFICATE-----
-MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
-TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
-cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
-WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
-ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
-MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
-h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
-0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
-A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
-T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
-B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
-B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
-KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
-OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
-jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
-qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
-rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
-HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
-hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
-ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
-3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
-NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
-ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
-TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
-jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
-oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
-4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
-mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
-emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
------END CERTIFICATE-----
-`,
- // CN = ISRG Root X2
- // TODO: Expires September 17, 2040 at 9:00:00 AM Pacific Daylight Time
- `
------BEGIN CERTIFICATE-----
-MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
-CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
-R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
-MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
-ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
-EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
-+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
-ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
-AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
-zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
-tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
-/q4AaOeMSQ+2b1tbFfLn
------END CERTIFICATE-----
-`,
-}
-
-func PinnedCertPool() *x509.CertPool {
- trustedCerts := x509.NewCertPool()
- for _, cert := range caCerts {
- trustedCerts.AppendCertsFromPEM([]byte(strings.TrimSpace(cert)))
- }
- return trustedCerts
-}
-
-type FakeTransport struct {
- CreateResponse func(req *http.Request) (*http.Response, error)
-}
-
-func (t FakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
- return t.CreateResponse(req)
-}
-
-type CustomTransport struct {
- T http.RoundTripper
-}
-
-func UserAgent() string {
- if len(feature.UserAgentSuffix.Load()) > 0 {
- return "TruffleHog " + feature.UserAgentSuffix.Load()
- }
- return "TruffleHog"
-}
-
-func (t *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) {
- req.Header.Add("User-Agent", UserAgent())
- return t.T.RoundTrip(req)
-}
-
-func NewCustomTransport(T http.RoundTripper) *CustomTransport {
- if T == nil {
- T = http.DefaultTransport
- }
- return &CustomTransport{T}
-}
-
-type InstrumentedTransport struct {
- T http.RoundTripper
-}
-
-func (t *InstrumentedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
-
- sanitizedURL := sanitizeURL(req.URL.String())
-
- // increment counter for the URL
- recordHTTPRequest(sanitizedURL)
-
- // Record start time for latency measurement
- start := time.Now()
-
- resp, err := t.T.RoundTrip(req)
-
- // Time the latency
- duration := time.Since(start)
-
- if err != nil {
- recordNetworkError(sanitizedURL)
- return nil, err
- }
-
- if resp != nil {
- // record latency, response size and increment counter for non-200 status code
- recordHTTPResponse(sanitizedURL, resp.StatusCode, duration.Seconds(), resp.ContentLength)
- }
-
- return resp, err
-}
-
-func NewInstrumentedTransport(T http.RoundTripper) *InstrumentedTransport {
- if T == nil {
- T = http.DefaultTransport
- }
- return &InstrumentedTransport{T}
-}
-
-func ConstantResponseHttpClient(statusCode int, body string) *http.Client {
- return &http.Client{
- Timeout: DefaultResponseTimeout,
- Transport: FakeTransport{
- CreateResponse: func(req *http.Request) (*http.Response, error) {
- return &http.Response{
- Request: req,
- Body: io.NopCloser(strings.NewReader(body)),
- StatusCode: statusCode,
- }, nil
- },
- },
- }
-}
-
-// ClientOption configures how we set up the client.
-type ClientOption func(*retryablehttp.Client)
-
-// WithCheckRetry allows setting a custom CheckRetry policy.
-func WithCheckRetry(cr retryablehttp.CheckRetry) ClientOption {
- return func(c *retryablehttp.Client) { c.CheckRetry = cr }
-}
-
-// WithBackoff allows setting a custom backoff policy.
-func WithBackoff(b retryablehttp.Backoff) ClientOption {
- return func(c *retryablehttp.Client) { c.Backoff = b }
-}
-
-// WithTimeout allows setting a custom timeout.
-func WithTimeout(timeout time.Duration) ClientOption {
- return func(c *retryablehttp.Client) { c.HTTPClient.Timeout = timeout }
-}
-
-// WithMaxRetries allows setting a custom maximum number of retries.
-func WithMaxRetries(retries int) ClientOption {
- return func(c *retryablehttp.Client) { c.RetryMax = retries }
-}
-
-// WithRetryWaitMin allows setting a custom minimum retry wait.
-func WithRetryWaitMin(wait time.Duration) ClientOption {
- return func(c *retryablehttp.Client) { c.RetryWaitMin = wait }
-}
-
-// WithRetryWaitMax allows setting a custom maximum retry wait.
-func WithRetryWaitMax(wait time.Duration) ClientOption {
- return func(c *retryablehttp.Client) { c.RetryWaitMax = wait }
-}
-
-func PinnedRetryableHttpClient() *http.Client {
- httpClient := retryablehttp.NewClient()
- httpClient.Logger = nil
- httpClient.HTTPClient.Transport = NewInstrumentedTransport(NewCustomTransport(&http.Transport{
- TLSClientConfig: &tls.Config{
- RootCAs: PinnedCertPool(),
- },
- Proxy: http.ProxyFromEnvironment,
- DialContext: (&net.Dialer{
- Timeout: 30 * time.Second,
- KeepAlive: 30 * time.Second,
- }).DialContext,
- ForceAttemptHTTP2: true,
- MaxIdleConns: 100,
- IdleConnTimeout: 90 * time.Second,
- TLSHandshakeTimeout: 10 * time.Second,
- ExpectContinueTimeout: 1 * time.Second,
- }))
- return httpClient.StandardClient()
-}
-
-func RetryableHTTPClient(opts ...ClientOption) *http.Client {
- httpClient := retryablehttp.NewClient()
- httpClient.RetryMax = 3
- httpClient.Logger = nil
- httpClient.HTTPClient.Transport = NewInstrumentedTransport(NewCustomTransport(nil))
-
- for _, opt := range opts {
- opt(httpClient)
- }
- return httpClient.StandardClient()
-}
-
-// RetryableHTTPClientTimeout returns a new http client with a specified timeout and RoundTripper transport
-func RetryableHTTPClientTimeout(timeOutSeconds int64, opts ...ClientOption) *http.Client {
- httpClient := retryablehttp.NewClient()
- httpClient.RetryMax = 3
- httpClient.Logger = nil
- httpClient.HTTPClient.Timeout = time.Duration(timeOutSeconds) * time.Second
- httpClient.HTTPClient.Transport = NewInstrumentedTransport(NewCustomTransport(nil))
-
- for _, opt := range opts {
- opt(httpClient)
- }
-
- standardClient := httpClient.StandardClient()
- standardClient.Timeout = httpClient.HTTPClient.Timeout
- return standardClient
-}
-
-const DefaultResponseTimeout = 5 * time.Second
-
-var saneTransport = &http.Transport{
- Proxy: http.ProxyFromEnvironment,
- DialContext: (&net.Dialer{
- Timeout: 2 * time.Second,
- KeepAlive: 5 * time.Second,
- }).DialContext,
- MaxIdleConns: 5,
- IdleConnTimeout: 5 * time.Second,
- TLSHandshakeTimeout: 3 * time.Second,
- ExpectContinueTimeout: 1 * time.Second,
-}
-
-func SaneHttpClient() *http.Client {
- httpClient := &http.Client{}
- httpClient.Timeout = DefaultResponseTimeout
- httpClient.Transport = NewInstrumentedTransport(NewCustomTransport(saneTransport))
- return httpClient
-}
-
-// SaneHttpClientTimeOut adds a custom timeout for some scanners
-func SaneHttpClientTimeOut(timeout time.Duration) *http.Client {
- httpClient := &http.Client{}
- httpClient.Timeout = timeout
- httpClient.Transport = NewInstrumentedTransport(NewCustomTransport(nil))
- return httpClient
-}
diff --git a/pkg/common/http_metrics.go b/pkg/common/http_metrics.go
deleted file mode 100644
index 868112d46e42..000000000000
--- a/pkg/common/http_metrics.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package common
-
-import (
- "net/url"
- "strconv"
-
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promauto"
-)
-
-var (
- httpRequestsTotal = promauto.NewCounterVec(
- prometheus.CounterOpts{
- Namespace: MetricsNamespace,
- Subsystem: "http_client",
- Name: "requests_total",
- Help: "Total number of HTTP requests made, labeled by URL.",
- },
- []string{"url"},
- )
-
- httpRequestDuration = promauto.NewHistogramVec(
- prometheus.HistogramOpts{
- Namespace: MetricsNamespace,
- Subsystem: "http_client",
- Name: "request_duration_seconds",
- Help: "HTTP request latency in seconds, labeled by URL.",
- Buckets: prometheus.DefBuckets,
- },
- []string{"url"},
- )
-
- httpNon200ResponsesTotal = promauto.NewCounterVec(
- prometheus.CounterOpts{
- Namespace: MetricsNamespace,
- Subsystem: "http_client",
- Name: "non_200_responses_total",
- Help: "Total number of non-200 HTTP responses, labeled by URL and status code.",
- },
- []string{"url", "status_code"},
- )
-
- httpResponseBodySizeBytes = promauto.NewHistogramVec(
- prometheus.HistogramOpts{
- Namespace: MetricsNamespace,
- Subsystem: "http_client",
- Name: "response_body_size_bytes",
- Help: "Size of HTTP response bodies in bytes, labeled by URL.",
- Buckets: prometheus.ExponentialBuckets(100, 10, 5), // [100B, 1KB, 10KB, 100KB, 1MB]
- },
- []string{"url"},
- )
-)
-
-// sanitizeURL sanitizes a URL to avoid high cardinality metrics.
-// It keeps only the host and path, removing query parameters, fragments, and user info.
-func sanitizeURL(rawURL string) string {
- if rawURL == "" {
- return "unknown"
- }
-
- parsedURL, err := url.Parse(rawURL)
- if err != nil {
- return "invalid_url"
- }
-
- // Build sanitized URL with just scheme, host, and path
- sanitized := &url.URL{
- Scheme: parsedURL.Scheme,
- Host: parsedURL.Host,
- Path: parsedURL.Path,
- }
-
- // If host is empty, try to extract from the raw URL
- if sanitized.Host == "" {
- // For relative URLs or malformed URLs, just use a placeholder
- return "relative_or_invalid"
- }
-
- // Normalize path
- if sanitized.Path == "" {
- sanitized.Path = "/"
- }
-
- // Limit path length to avoid extremely long paths creating high cardinality
- if len(sanitized.Path) > 100 {
- sanitized.Path = sanitized.Path[:100] + "..."
- }
-
- result := sanitized.String()
-
- // Final fallback to avoid empty strings
- if result == "" {
- return "unknown"
- }
-
- return result
-}
-
-// recordHTTPRequest records metrics for an HTTP request.
-func recordHTTPRequest(sanitizedURL string) {
- httpRequestsTotal.WithLabelValues(sanitizedURL).Inc()
-}
-
-// recordHTTPResponse records metrics for an HTTP response.
-func recordHTTPResponse(sanitizedURL string, statusCode int, durationSeconds float64, contentLength int64) {
- // Record latency
- httpRequestDuration.WithLabelValues(sanitizedURL).Observe(durationSeconds)
-
- // Record non-200 responses
- if statusCode != 200 {
- httpNon200ResponsesTotal.WithLabelValues(sanitizedURL, strconv.Itoa(statusCode)).Inc()
- }
-
- // Record response body size if known
- if contentLength >= 0 {
- httpResponseBodySizeBytes.WithLabelValues(sanitizedURL).Observe(float64(contentLength))
- }
-}
-
-// recordNetworkError records metrics for failed HTTP response
-func recordNetworkError(sanitizedURL string) {
- httpNon200ResponsesTotal.WithLabelValues(sanitizedURL, "network_error").Inc()
-}
diff --git a/pkg/common/http_test.go b/pkg/common/http_test.go
deleted file mode 100644
index 34d6028865ce..000000000000
--- a/pkg/common/http_test.go
+++ /dev/null
@@ -1,508 +0,0 @@
-package common
-
-import (
- "context"
- "math"
- "net/http"
- "net/http/httptest"
- "slices"
- "strings"
- "testing"
- "time"
-
- "github.com/hashicorp/go-retryablehttp"
- "github.com/prometheus/client_golang/prometheus/testutil"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestRetryableHTTPClientCheckRetry(t *testing.T) {
- testCases := []struct {
- name string
- responseStatus int
- checkRetry retryablehttp.CheckRetry
- expectedRetries int
- }{
- {
- name: "Retry on 500 status, give up after 3 retries",
- responseStatus: http.StatusInternalServerError, // Server error status
- checkRetry: func(ctx context.Context, resp *http.Response, err error) (bool, error) {
- if err != nil {
- t.Errorf("expected response with 500 status, got error: %v", err)
- return false, err
- }
- // The underlying transport will retry on 500 status.
- if resp.StatusCode == http.StatusInternalServerError {
- return true, nil
- }
- return false, nil
- },
- expectedRetries: 3,
- },
- {
- name: "No retry on 400 status",
- responseStatus: http.StatusBadRequest, // Client error status
- checkRetry: func(ctx context.Context, resp *http.Response, err error) (bool, error) {
- // Do not retry on client errors.
- return false, nil
- },
- expectedRetries: 0,
- },
- {
- name: "Retry on 429 status, give up after 3 retries",
- responseStatus: http.StatusTooManyRequests,
- checkRetry: func(ctx context.Context, resp *http.Response, err error) (bool, error) {
- if err != nil {
- t.Errorf("expected response with 429 status, got error: %v", err)
- return false, err
- }
- // The underlying transport will retry on 429 status.
- if resp.StatusCode == http.StatusTooManyRequests {
- return true, nil
- }
- return false, nil
- },
- expectedRetries: 3,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- var retryCount int
-
- // Do not count the initial request as a retry.
- i := 0
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if i != 0 {
- retryCount++
- }
- i++
- w.WriteHeader(tc.responseStatus)
- }))
- defer server.Close()
-
- ctx := context.Background()
- client := RetryableHTTPClient(WithCheckRetry(tc.checkRetry), WithTimeout(10*time.Millisecond), WithRetryWaitMin(1*time.Millisecond))
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, server.URL, nil)
- assert.NoError(t, err)
-
- // Bad linter, there is no body to close.
- _, err = client.Do(req) //nolint:bodyclose
- if slices.Contains([]int{http.StatusInternalServerError, http.StatusTooManyRequests}, tc.responseStatus) {
- // The underlying transport will retry on 500 and 429 status.
- assert.Error(t, err)
- }
-
- assert.Equal(t, tc.expectedRetries, retryCount, "Retry count does not match expected")
- })
- }
-}
-
-func TestRetryableHTTPClientMaxRetry(t *testing.T) {
- testCases := []struct {
- name string
- responseStatus int
- maxRetries int
- expectedRetries int
- }{
- {
- name: "Max retries with 500 status",
- responseStatus: http.StatusInternalServerError,
- maxRetries: 2,
- expectedRetries: 2,
- },
- {
- name: "Max retries with 429 status",
- responseStatus: http.StatusTooManyRequests,
- maxRetries: 1,
- expectedRetries: 1,
- },
- {
- name: "Max retries with 200 status",
- responseStatus: http.StatusOK,
- maxRetries: 3,
- expectedRetries: 0,
- },
- {
- name: "Max retries with 400 status",
- responseStatus: http.StatusBadRequest,
- maxRetries: 3,
- expectedRetries: 0,
- },
- {
- name: "Max retries with 401 status",
- responseStatus: http.StatusUnauthorized,
- maxRetries: 3,
- expectedRetries: 0,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- var retryCount int
-
- i := 0
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if i != 0 {
- retryCount++
- }
- i++
- w.WriteHeader(tc.responseStatus)
- }))
- defer server.Close()
-
- client := RetryableHTTPClient(
- WithMaxRetries(tc.maxRetries),
- WithTimeout(10*time.Millisecond),
- WithRetryWaitMin(1*time.Millisecond),
- )
-
- ctx := context.Background()
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, server.URL, nil)
- assert.NoError(t, err)
-
- // Bad linter, there is no body to close.
- _, err = client.Do(req) //nolint:bodyclose
- if err != nil && tc.responseStatus == http.StatusOK {
- assert.Error(t, err)
- }
-
- assert.Equal(t, tc.expectedRetries, retryCount, "Retry count does not match expected")
- })
- }
-}
-
-func TestRetryableHTTPClientBackoff(t *testing.T) {
- testCases := []struct {
- name string
- responseStatus int
- expectedRetries int
- backoffPolicy retryablehttp.Backoff
- expectedBackoffs []time.Duration
- }{
- {
- name: "Custom backoff on 500 status",
- responseStatus: http.StatusInternalServerError,
- expectedRetries: 3,
- backoffPolicy: func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
- switch attemptNum {
- case 1:
- return 1 * time.Millisecond
- case 2:
- return 2 * time.Millisecond
- case 3:
- return 4 * time.Millisecond
- default:
- return max
- }
- },
- expectedBackoffs: []time.Duration{1 * time.Millisecond, 2 * time.Millisecond, 4 * time.Millisecond},
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- var actualBackoffs []time.Duration
- var lastTime time.Time
-
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- now := time.Now()
- if !lastTime.IsZero() {
- actualBackoffs = append(actualBackoffs, now.Sub(lastTime))
- }
- lastTime = now
- w.WriteHeader(tc.responseStatus)
- }))
- defer server.Close()
-
- ctx := context.Background()
- client := RetryableHTTPClient(
- WithBackoff(tc.backoffPolicy),
- WithTimeout(10*time.Millisecond),
- WithRetryWaitMin(1*time.Millisecond),
- WithRetryWaitMax(10*time.Millisecond),
- )
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, server.URL, nil)
- assert.NoError(t, err)
-
- _, err = client.Do(req) //nolint:bodyclose
- assert.Error(t, err, "Expected error due to 500 status")
-
- assert.Len(t, actualBackoffs, tc.expectedRetries, "Unexpected number of backoffs")
-
- for i, expectedBackoff := range tc.expectedBackoffs {
- if i < len(actualBackoffs) {
- // Allow some deviation in timing due to processing delays.
- assert.Less(t, math.Abs(float64(actualBackoffs[i]-expectedBackoff)), float64(15*time.Millisecond), "Unexpected backoff duration")
- }
- }
- })
- }
-}
-
-func TestRetryableHTTPClientTimeout(t *testing.T) {
- testCases := []struct {
- name string
- timeoutSeconds int64
- expectedTimeout time.Duration
- }{
- {
- name: "5 second timeout",
- timeoutSeconds: 5,
- expectedTimeout: 5 * time.Second,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
-
- // Call the function with the test timeout value
- client := RetryableHTTPClientTimeout(tc.timeoutSeconds)
-
- // Verify that the timeout is set correctly
- assert.Equal(t, tc.expectedTimeout, client.Timeout, "HTTP client timeout does not match expected value")
-
- // Verify that the transport is a custom transport
- _, isRoundTripperTransport := client.Transport.(*retryablehttp.RoundTripper)
- assert.True(t, isRoundTripperTransport, "HTTP client transport is not a retryablehttp.RoundTripper")
- })
- }
-}
-
-func TestSanitizeURL(t *testing.T) {
- testCases := []struct {
- name string
- input string
- expected string
- }{
- {
- name: "valid https URL",
- input: "https://api.example.com/v1/users",
- expected: "https://api.example.com/v1/users",
- },
- {
- name: "URL with query parameters",
- input: "https://api.example.com/search?q=secret&limit=10",
- expected: "https://api.example.com/search",
- },
- {
- name: "URL with fragment",
- input: "https://example.com/page#section",
- expected: "https://example.com/page",
- },
- {
- name: "URL with user info",
- input: "https://user:pass@api.example.com/path",
- expected: "https://api.example.com/path",
- },
- {
- name: "empty URL",
- input: "",
- expected: "unknown",
- },
- {
- name: "invalid URL",
- input: "not-a-url",
- expected: "relative_or_invalid",
- },
- {
- name: "very long path",
- input: "https://example.com/" + strings.Repeat("a", 150),
- expected: "https://example.com/" + strings.Repeat("a", 99) + "...", // 99 + 1 ("/") = 100 chars
- },
- {
- name: "root path",
- input: "https://example.com",
- expected: "https://example.com/",
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- result := sanitizeURL(tc.input)
- assert.Equal(t, tc.expected, result)
- })
- }
-}
-
-func TestSaneHttpClientMetrics(t *testing.T) {
- // Create a test server that returns different status codes
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case "/success":
- w.WriteHeader(http.StatusOK)
- _, _ = w.Write([]byte("success"))
- case "/error":
- w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write([]byte("error"))
- case "/notfound":
- w.WriteHeader(http.StatusNotFound)
- _, _ = w.Write([]byte("not found"))
- default:
- w.WriteHeader(http.StatusOK)
- _, _ = w.Write([]byte("default"))
- }
- }))
- defer server.Close()
-
- // Create a SaneHttpClient
- client := SaneHttpClient()
-
- testCases := []struct {
- name string
- path string
- expectedStatusCode int
- expectsNon200 bool
- }{
- {
- name: "successful request",
- path: "/success",
- expectedStatusCode: 200,
- expectsNon200: false,
- },
- {
- name: "server error request",
- path: "/error",
- expectedStatusCode: 500,
- expectsNon200: true,
- },
- {
- name: "not found request",
- path: "/notfound",
- expectedStatusCode: 404,
- expectsNon200: true,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- var requestURL string
- if strings.HasPrefix(tc.path, "http") {
- requestURL = tc.path
- } else {
- requestURL = server.URL + tc.path
- }
-
- // Get initial metric values
- sanitizedURL := sanitizeURL(requestURL)
- initialRequestsTotal := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))
-
- // Make the request
- resp, err := client.Get(requestURL)
-
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
-
- // Check that request counter was incremented
- requestsTotal := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))
- assert.Equal(t, initialRequestsTotal+1, requestsTotal)
- })
- }
-}
-
-func TestRetryableHttpClientMetrics(t *testing.T) {
- // Create a test server that returns different status codes
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case "/success":
- w.WriteHeader(http.StatusOK)
- _, _ = w.Write([]byte("success"))
- case "/error":
- w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write([]byte("error"))
- case "/notfound":
- w.WriteHeader(http.StatusNotFound)
- _, _ = w.Write([]byte("not found"))
- default:
- w.WriteHeader(http.StatusOK)
- _, _ = w.Write([]byte("default"))
- }
- }))
- defer server.Close()
-
- // Create a RetryableHttpClient
- client := RetryableHTTPClient()
-
- testCases := []struct {
- name string
- path string
- expectedStatusCode int
- }{
- {
- name: "successful request",
- path: "/success",
- expectedStatusCode: 200,
- },
- {
- name: "not found request",
- path: "/notfound",
- expectedStatusCode: 404,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- var requestURL string
- if strings.HasPrefix(tc.path, "http") {
- requestURL = tc.path
- } else {
- requestURL = server.URL + tc.path
- }
-
- // Get initial metric values
- sanitizedURL := sanitizeURL(requestURL)
- initialRequestsTotal := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))
-
- // Make the request
- resp, err := client.Get(requestURL)
-
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
-
- // Check that request counter was incremented
- requestsTotal := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))
- assert.Equal(t, initialRequestsTotal+1, requestsTotal)
- })
- }
-}
-
-func TestInstrumentedTransport(t *testing.T) {
- // Create a mock transport that we can control
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusOK)
- _, _ = w.Write([]byte("test response"))
- }))
- defer server.Close()
-
- // Create instrumented transport
- transport := NewInstrumentedTransport(nil)
- client := &http.Client{
- Transport: transport,
- Timeout: 5 * time.Second,
- }
-
- // Get initial metric value
- sanitizedURL := sanitizeURL(server.URL)
- initialCount := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))
-
- // Make a request
- resp, err := client.Get(server.URL)
- require.NoError(t, err)
- defer resp.Body.Close()
-
- // Verify the request was successful
- assert.Equal(t, http.StatusOK, resp.StatusCode)
-
- // Verify metrics were recorded
- finalCount := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))
- assert.Equal(t, initialCount+1, finalCount)
-
- // Note: Testing histogram metrics is complex due to the way Prometheus handles them
- // The main thing is that the request completed successfully and counters were incremented
-}
diff --git a/pkg/common/metrics.go b/pkg/common/metrics.go
deleted file mode 100644
index d6c45864cfbf..000000000000
--- a/pkg/common/metrics.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package common
-
-const (
- // MetricsNamespace is the namespace for all metrics.
- MetricsNamespace = "trufflehog"
- // MetricsSubsystem is the subsystem for all metrics.
- MetricsSubsystem = "scanner"
-)
diff --git a/pkg/common/patterns.go b/pkg/common/patterns.go
deleted file mode 100644
index 6ee68b5221f8..000000000000
--- a/pkg/common/patterns.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package common
-
-import (
- "fmt"
- "regexp"
- "strconv"
- "strings"
-)
-
-const EmailPattern = `\b((?i)(?:[a-z0-9!#$%&'*+/=?^_\x60{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_\x60{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\]))\b`
-const SubDomainPattern = `\b([A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?)\b`
-const UUIDPattern = `\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b`
-const UUIDPatternUpperCase = `\b([0-9A-Z]{8}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{12})\b`
-
-const RegexPattern = "0-9a-z"
-const AlphaNumPattern = "0-9a-zA-Z"
-const HexPattern = "0-9a-f"
-
-type RegexState struct {
- compiledRegex *regexp.Regexp
-}
-
-// Custom Regex functions
-func BuildRegex(pattern string, specialChar string, length int) string {
- return fmt.Sprintf(`\b([%s%s]{%s})\b`, pattern, specialChar, strconv.Itoa(length))
-}
-
-func BuildRegexJWT(firstRange, secondRange, thirdRange string) string {
- if RangeValidation(firstRange) || RangeValidation(secondRange) || RangeValidation(thirdRange) {
- panic("Min value should not be greater than or equal to max")
- }
- return fmt.Sprintf(`\b(ey[%s]{%s}.ey[%s-\/_]{%s}.[%s-\/_]{%s})\b`, AlphaNumPattern, firstRange, AlphaNumPattern, secondRange, AlphaNumPattern, thirdRange)
-}
-
-func RangeValidation(rangeInput string) bool {
- range_split := strings.Split(rangeInput, ",")
- range_min, _ := strconv.ParseInt(strings.TrimSpace(range_split[0]), 10, 0)
- range_max, _ := strconv.ParseInt(strings.TrimSpace(range_split[1]), 10, 0)
- return range_min >= range_max
-}
-
-func ToUpperCase(input string) string {
- return strings.ToUpper(input)
-}
-
-func (r RegexState) Matches(data []byte) []string {
- matches := r.compiledRegex.FindAllStringSubmatch(string(data), -1)
-
- res := make([]string, 0, len(matches))
-
- // trim off all white spaces and different quote types ('"") & some special characters (,;).
- for i := range matches {
- res = append(res, strings.Trim(strings.TrimSpace(matches[i][1]), `"' ),;`))
- }
-
- return res
-}
-
-// UsernameRegexCheck constructs an username usernameRegex pattern from a given pattern of excluded characters.
-func UsernameRegexCheck(pattern string) RegexState {
- raw := fmt.Sprintf(`(?im)(?:user|usr)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:%+v]{4,40})\b`, pattern)
-
- return RegexState{regexp.MustCompile(raw)}
-}
-
-// PasswordRegexCheck constructs an username usernameRegex pattern from a given pattern of excluded characters.
-func PasswordRegexCheck(pattern string) RegexState {
- raw := fmt.Sprintf(`(?im)(?:pass|password)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:%+v]{4,40})`, pattern)
-
- return RegexState{regexp.MustCompile(raw)}
-}
diff --git a/pkg/common/patterns_test.go b/pkg/common/patterns_test.go
deleted file mode 100644
index a52f517e0459..000000000000
--- a/pkg/common/patterns_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package common
-
-import (
- "regexp"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-const (
- usernamePattern = `?()/\+=\s\n`
- passwordPattern = `^<>;.*&|£\n\s`
- usernameRegex = `(?im)(?:user|usr)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:?()/\+=\s\n]{4,40})\b`
- passwordRegex = `(?im)(?:pass|password)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:^<>;.*&|£\n\s]{4,40})`
-)
-
-func TestEmailRegexCheck(t *testing.T) {
- testEmails := `
- // positive cases
- standard email = john.doe@example.com
- subdomain email = jane_doe123@sub.domain.co.us
- organization email = alice.smith@test.org
- test email = bob@test.name
- with tag email = user.name+tag@domain.com
- hyphen domain = info@my-site.net
- service email = contact@web-service.io
- underscore email = example_user@domain.info
- department email = first.last@department.company.edu
- alphanumeric email = user1234@domain.co
- local server email = admin@local-server.local
- dot email = test.email@my-email-service.xyz
- special char email = special@characters.com
- support email = support@customer-service.org
- insenstive email = ADMIN@example.com
- insenstive domain = ADMIN@COMPANY.COM
- mix email = USER123xyz@local-Server.local
-
- // negative cases
- not an email = abc.123@z
- looks like email = test@user <- no domain
- random text = here's some information about local-user@edu user
- `
-
- expectedStr := []string{
- "john.doe@example.com", "jane_doe123@sub.domain.co.us",
- "alice.smith@test.org", "bob@test.name", "user.name+tag@domain.com",
- "info@my-site.net", "contact@web-service.io", "example_user@domain.info",
- "first.last@department.company.edu", "user1234@domain.co", "admin@local-server.local",
- "test.email@my-email-service.xyz", "special@characters.com", "support@customer-service.org",
- "ADMIN@example.com", "ADMIN@COMPANY.COM", "USER123xyz@local-Server.local",
- }
-
- emailRegex := regexp.MustCompile(EmailPattern)
-
- emailMatches := emailRegex.FindAllString(testEmails, -1)
-
- assert.Exactly(t, emailMatches, expectedStr)
-
-}
-
-func TestUsernameRegexCheck(t *testing.T) {
- usernameRegexPat := UsernameRegexCheck(usernamePattern)
-
- expectedRegexPattern := regexp.MustCompile(usernameRegex)
-
- if usernameRegexPat.compiledRegex.String() != expectedRegexPattern.String() {
- t.Errorf("\n got %v \n want %v", usernameRegexPat.compiledRegex, expectedRegexPattern)
- }
-
- testString := `username = "johnsmith123"
- username='johnsmith123'
- username:="johnsmith123"
- username:="johnsmith123",
- username:="johnsmith123";
- username = johnsmith123
- username=johnsmith123`
-
- expectedStr := []string{
- "johnsmith123",
- "johnsmith123",
- "johnsmith123",
- "johnsmith123",
- "johnsmith123",
- "johnsmith123",
- "johnsmith123",
- }
-
- usernameRegexMatches := usernameRegexPat.Matches([]byte(testString))
-
- assert.Exactly(t, usernameRegexMatches, expectedStr)
-
-}
-
-func TestPasswordRegexCheck(t *testing.T) {
- passwordRegexPat := PasswordRegexCheck(passwordPattern)
-
- expectedRegexPattern := regexp.MustCompile(passwordRegex)
- assert.Equal(t, passwordRegexPat.compiledRegex, expectedRegexPattern)
-
- testString := `password = "johnsmith123$!"
- password='johnsmith123$!'
- password:="johnsmith123$!"
- password:="johnsmith123$!",
- password:="johnsmith123$!";
- password:="johnsmi',th123$!";
- password = johnsmith123$!
- password=johnsmith123$!
- PasswordAuthenticator(username, "johnsmith123$!")`
-
- expectedStr := []string{
- "johnsmith123$!",
- "johnsmith123$!",
- "johnsmith123$!",
- "johnsmith123$!",
- "johnsmith123$!",
- "johnsmi',th123$!",
- "johnsmith123$!",
- "johnsmith123$!",
- "johnsmith123$!",
- }
-
- passwordRegexMatches := passwordRegexPat.Matches([]byte(testString))
-
- assert.Exactly(t, passwordRegexMatches, expectedStr)
-
-}
diff --git a/pkg/common/recover.go b/pkg/common/recover.go
deleted file mode 100644
index 4d9d779ac6bc..000000000000
--- a/pkg/common/recover.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package common
-
-import (
- "fmt"
- "os"
- "runtime/debug"
- "time"
-
- "github.com/getsentry/sentry-go"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-// Recover handles panics and reports to Sentry.
-func Recover(ctx context.Context) {
- if err := recover(); err != nil {
- panicStack := string(debug.Stack())
- if eventID := sentry.CurrentHub().Recover(err); eventID != nil {
- ctx.Logger().Info("panic captured", "event_id", *eventID)
- }
- ctx.Logger().Error(fmt.Errorf("panic"), panicStack,
- "recover", err,
- )
- if !sentry.Flush(time.Second * 5) {
- ctx.Logger().Info("sentry flush failed")
- }
- }
-}
-
-// RecoverWithHandler handles panics and reports to Sentry, then turns control
-// over to a provided function. This permits extra reporting in the same scope
-// without re-panicking, as recover() clears the state after it's called. Does
-// NOT block to flush sentry report.
-func RecoverWithHandler(ctx context.Context, callback func(error)) {
- if err := recover(); err != nil {
- panicStack := string(debug.Stack())
- if eventID := sentry.CurrentHub().Recover(err); eventID != nil {
- ctx.Logger().Info("panic captured", "event_id", *eventID)
- }
- ctx.Logger().Error(fmt.Errorf("panic"), panicStack,
- "recover", err,
- )
-
- switch v := err.(type) {
- case error:
- callback(fmt.Errorf("panic: %w", v))
- default:
- callback(fmt.Errorf("panic: %v", v))
- }
- }
-}
-
-// RecoverWithExit handles panics and reports to Sentry before exiting.
-func RecoverWithExit(ctx context.Context) {
- if err := recover(); err != nil {
- panicStack := string(debug.Stack())
- if eventID := sentry.CurrentHub().Recover(err); eventID != nil {
- ctx.Logger().Info("panic captured", "event_id", *eventID)
- }
- ctx.Logger().Error(fmt.Errorf("panic"), "recovered from panic before exiting",
- "stack-trace", panicStack,
- "recover", err,
- )
- if !sentry.Flush(time.Second * 5) {
- ctx.Logger().Info("sentry flush failed")
- }
- os.Exit(1)
- }
-}
diff --git a/pkg/common/secrets.go b/pkg/common/secrets.go
deleted file mode 100644
index cc759248eed8..000000000000
--- a/pkg/common/secrets.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package common
-
-import (
- "context"
- "fmt"
- "os"
- "time"
-
- secretmanager "cloud.google.com/go/secretmanager/apiv1"
- "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
- "github.com/joho/godotenv"
- "github.com/pkg/errors"
-)
-
-type Secret struct{ kv map[string]string }
-
-func (s *Secret) MustGetField(name string) string {
- val, ok := s.kv[name]
- if !ok {
- panic(errors.Errorf("field %s not found", name))
- }
- return val
-}
-
-func GetSecretFromEnv(filename string) (secret *Secret, err error) {
- data, err := godotenv.Read(filename)
- if err != nil {
- return nil, err
- }
- return &Secret{kv: data}, nil
-}
-
-func GetTestSecret(ctx context.Context) (secret *Secret, err error) {
- filename := os.Getenv("TEST_SECRET_FILE")
- if len(filename) > 0 {
- return GetSecretFromEnv(filename)
- }
-
- return GetSecret(ctx, "trufflehog-testing", "test")
-}
-
-func GetSecret(ctx context.Context, gcpProject, name string) (secret *Secret, err error) {
- ctx, cancel := context.WithTimeout(ctx, time.Second*10)
- defer cancel()
-
- filename := os.Getenv("TEST_SECRET_FILE")
- if len(filename) > 0 {
- return GetSecretFromEnv(filename)
- }
-
- parent := fmt.Sprintf("projects/%s/secrets/%s/versions/latest", gcpProject, name)
-
- client, err := secretmanager.NewClient(ctx)
- if err != nil {
- return nil, errors.Errorf("failed to create secretmanager client: %v", err)
- }
- defer client.Close()
-
- req := &secretmanagerpb.AccessSecretVersionRequest{
- Name: parent,
- }
-
- result, err := client.AccessSecretVersion(ctx, req)
- if err != nil {
- return nil, errors.Errorf("failed to access secret version: %v", err)
- }
-
- data, err := godotenv.Unmarshal(string(result.Payload.Data))
- if err != nil {
- return nil, err
- }
-
- return &Secret{kv: data}, nil
-}
diff --git a/pkg/common/utils.go b/pkg/common/utils.go
deleted file mode 100644
index e235bd1f3ae0..000000000000
--- a/pkg/common/utils.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package common
-
-import (
- "bufio"
- "crypto/rand"
- "io"
- "math/big"
- mrand "math/rand"
- "strings"
-)
-
-func AddStringSliceItem(item string, slice *[]string) {
- for _, i := range *slice {
- if i == item {
- return
- }
- }
- *slice = append(*slice, item)
-}
-
-func RemoveStringSliceItem(item string, slice *[]string) {
- for i, listItem := range *slice {
- if item == listItem {
- (*slice)[i] = (*slice)[len(*slice)-1]
- *slice = (*slice)[:len(*slice)-1]
- }
- }
-}
-
-func ResponseContainsSubstring(reader io.ReadCloser, target string) (bool, error) {
- scanner := bufio.NewScanner(reader)
- for scanner.Scan() {
- if strings.Contains(scanner.Text(), target) {
- return true, nil
- }
- }
- if err := scanner.Err(); err != nil {
- return false, err
- }
- return false, nil
-}
-
-var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
-
-// RandomID returns a random string of the given length.
-func RandomID(length int) string {
- b := make([]rune, length)
- for i := range b {
- randInt, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
- b[i] = letters[randInt.Int64()]
- }
-
- return string(b)
-}
-
-// SliceContainsString searches a slice to determine if it contains a specified string.
-// Returns the index of the first match in the slice.
-func SliceContainsString(origTargetString string, stringSlice []string, ignoreCase bool) (bool, string, int) {
- targetString := origTargetString
- if ignoreCase {
- targetString = strings.ToLower(origTargetString)
- }
- for i, origStringFromSlice := range stringSlice {
- stringFromSlice := origStringFromSlice
- if ignoreCase {
- stringFromSlice = strings.ToLower(origStringFromSlice)
- }
- if targetString == stringFromSlice {
- return true, targetString, i
- }
- }
- return false, "", 0
-}
-
-// GoFakeIt Password generator does not guarantee inclusion of characters.
-// Using a custom random password generator with guaranteed inclusions (atleast) of lower, upper, numeric and special characters
-func GenerateRandomPassword(lower, upper, numeric, special bool, length int) string {
- if length < 1 {
- return ""
- }
-
- var password []rune
- var required []rune
- var allowed []rune
-
- lowerChars := []rune("abcdefghijklmnopqrstuvwxyz")
- upperChars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
- specialChars := []rune("!@#$%^&*()-_=+[]{}|;:',.<>?/")
- numberChars := []rune("0123456789")
-
- // Ensure inclusion from each requested category
- if lower {
- rand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(lowerChars))))
- ch := lowerChars[rand.Int64()]
- required = append(required, ch)
- allowed = append(allowed, lowerChars...)
- }
- if upper {
- rand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(upperChars))))
- ch := upperChars[rand.Int64()]
- required = append(required, ch)
- allowed = append(allowed, upperChars...)
- }
- if numeric {
- rand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(numberChars))))
- ch := numberChars[rand.Int64()]
- required = append(required, ch)
- allowed = append(allowed, numberChars...)
- }
- if special {
- rand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(specialChars))))
- ch := specialChars[rand.Int64()]
- required = append(required, ch)
- allowed = append(allowed, specialChars...)
- }
-
- if len(allowed) == 0 {
- return "" // No character sets enabled
- }
-
- // Fill the rest of the password
- for i := 0; i < length-len(required); i++ {
- rand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(allowed))))
- ch := allowed[rand.Int64()]
- password = append(password, ch)
- }
-
- // Combine required and random characters, then shuffle
- password = append(password, required...)
- mrand.Shuffle(len(password), func(i, j int) {
- password[i], password[j] = password[j], password[i]
- })
-
- return string(password)
-}
diff --git a/pkg/common/utils_test.go b/pkg/common/utils_test.go
deleted file mode 100644
index 8bddc4e470c1..000000000000
--- a/pkg/common/utils_test.go
+++ /dev/null
@@ -1,279 +0,0 @@
-package common
-
-import (
- "io"
- "reflect"
- "strings"
- "testing"
- "unicode"
-)
-
-func TestAddItem(t *testing.T) {
- type Case struct {
- Slice []string
- Modifier []string
- Expected []string
- }
- tests := map[string]Case{
- "newItem": {
- Slice: []string{"a", "b", "c"},
- Modifier: []string{"d"},
- Expected: []string{"a", "b", "c", "d"},
- },
- "newDuplicate": {
- Slice: []string{"a", "b", "c"},
- Modifier: []string{"c"},
- Expected: []string{"a", "b", "c"},
- },
- }
-
- for name, test := range tests {
- for _, item := range test.Modifier {
- AddStringSliceItem(item, &test.Slice)
- }
-
- if !reflect.DeepEqual(test.Slice, test.Expected) {
- t.Errorf("%s: expected:%v, got:%v", name, test.Expected, test.Slice)
- }
- }
-}
-
-func TestRemoveItem(t *testing.T) {
- type Case struct {
- Slice []string
- Modifier []string
- Expected []string
- }
- tests := map[string]Case{
- "existingItemEnd": {
- Slice: []string{"a", "b", "c"},
- Modifier: []string{"c"},
- Expected: []string{"a", "b"},
- },
- "existingItemMiddle": {
- Slice: []string{"a", "b", "c"},
- Modifier: []string{"b"},
- Expected: []string{"a", "c"},
- },
- "existingItemBeginning": {
- Slice: []string{"a", "b", "c"},
- Modifier: []string{"a"},
- Expected: []string{"c", "b"},
- },
- "nonExistingItem": {
- Slice: []string{"a", "b", "c"},
- Modifier: []string{"d"},
- Expected: []string{"a", "b", "c"},
- },
- }
-
- for name, test := range tests {
- for _, item := range test.Modifier {
- RemoveStringSliceItem(item, &test.Slice)
- }
-
- if !reflect.DeepEqual(test.Slice, test.Expected) {
- t.Errorf("%s: expected:%v, got:%v", name, test.Expected, test.Slice)
- }
- }
-}
-
-// Test ParseResponseForKeywords with a reader that contains the keyword and a reader that doesn't.
-func TestParseResponseForKeywords(t *testing.T) {
- testCases := []struct {
- name string
- input string
- keyword string
- expected bool
- }{
- {
- name: "Should find keyword",
- input: "ey: abc",
- keyword: "ey",
- expected: true,
- },
- {
- name: "Should not find keyword",
- input: "fake response",
- keyword: "ey",
- expected: false,
- },
- {
- name: "Empty string",
- input: "",
- keyword: "ey",
- expected: false,
- },
- {
- name: "Keyword at end",
- input: "abc ey",
- keyword: "ey",
- expected: true,
- },
- {
- name: "Keyword at start",
- input: "ey abc",
- keyword: "ey",
- expected: true,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- testReader := strings.NewReader(tc.input)
- testReadCloser := io.NopCloser(testReader)
- found, err := ResponseContainsSubstring(testReadCloser, tc.keyword)
-
- if err != nil {
- t.Errorf("Error: %v", err)
- }
-
- if found != tc.expected {
- t.Errorf("Expected %v, got %v", tc.expected, found)
- }
- })
- }
-}
-
-func TestSliceContainsString(t *testing.T) {
- testCases := []struct {
- name string
- slice []string
- target string
- expectedBool bool
- expectedString string
- expectedIndex int
- ignoreCase bool
- }{
- {
- name: "matching case, target exists",
- slice: []string{"one", "two", "three"},
- target: "two",
- expectedBool: true,
- expectedString: "two",
- expectedIndex: 1,
- ignoreCase: false,
- },
- {
- name: "non-matching case, target exists, ignore case",
- slice: []string{"one", "two", "three"},
- target: "Two",
- expectedBool: true,
- expectedString: "two",
- expectedIndex: 1,
- ignoreCase: true,
- },
- {
- name: "non-matching case, target in wrong case, case respected",
- slice: []string{"one", "two", "three"},
- target: "Two",
- expectedBool: false,
- expectedString: "",
- expectedIndex: 0,
- ignoreCase: false,
- },
- {
- name: "target not in slice",
- slice: []string{"one", "two", "three"},
- target: "four",
- expectedBool: false,
- expectedString: "",
- expectedIndex: 0,
- ignoreCase: false,
- },
- }
- for _, testCase := range testCases {
- resultBool, resultString, resultIndex := SliceContainsString(testCase.target, testCase.slice, testCase.ignoreCase)
- if resultBool != testCase.expectedBool {
- t.Errorf("%s: bool values do not match. Got: %t, expected: %t", testCase.name, resultBool, testCase.expectedBool)
- }
- if resultString != testCase.expectedString {
- t.Errorf("%s: string values do not match. Got: %s, expected: %s", testCase.name, resultString, testCase.expectedString)
- }
- if resultIndex != testCase.expectedIndex {
- t.Errorf("%s: index values do not match. Got: %d, expected: %d", testCase.name, resultIndex, testCase.expectedIndex)
- }
- }
-}
-
-func TestGenerateRandomPassword_Length(t *testing.T) {
- pass := GenerateRandomPassword(true, true, true, true, 16)
- if len(pass) != 16 {
- t.Errorf("expected length 16, got %d", len(pass))
- }
-}
-
-func TestGenerateRandomPassword_Empty(t *testing.T) {
- pass := GenerateRandomPassword(false, false, false, false, 10)
- if pass != "" {
- t.Errorf("expected empty string, got %q", pass)
- }
-}
-
-func TestGenerateRandomPassword_RequiredSets(t *testing.T) {
- tests := []struct {
- name string
- lower bool
- upper bool
- numeric bool
- special bool
- }{
- {"lower only", true, false, false, false},
- {"upper only", false, true, false, false},
- {"numeric only", false, false, true, false},
- {"special only", false, false, false, true},
- {"all", true, true, true, true},
- {"lower+upper", true, true, false, false},
- {"lower+numeric", true, false, true, false},
- {"upper+special", false, true, false, true},
- }
-
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- pass := GenerateRandomPassword(tc.lower, tc.upper, tc.numeric, tc.special, 12)
- if len(pass) != 12 {
- t.Errorf("expected length 12, got %d", len(pass))
- }
- if tc.lower && !contains(pass, unicode.IsLower) {
- t.Errorf("expected at least one lowercase letter")
- }
- if tc.upper && !contains(pass, unicode.IsUpper) {
- t.Errorf("expected at least one uppercase letter")
- }
- if tc.numeric && !contains(pass, unicode.IsDigit) {
- t.Errorf("expected at least one digit")
- }
- if tc.special && !containsSpecial(pass) {
- t.Errorf("expected at least one special character")
- }
- })
- }
-}
-
-func TestGenerateRandomPassword_ShortLength(t *testing.T) {
- pass := GenerateRandomPassword(true, true, true, true, 0)
- if pass != "" {
- t.Errorf("expected empty string for length 0, got %q", pass)
- }
-}
-
-func contains(s string, fn func(rune) bool) bool {
- for _, r := range s {
- if fn(r) {
- return true
- }
- }
- return false
-}
-
-func containsSpecial(s string) bool {
- specials := "!@#$%^&*()-_=+[]{}|;:',.<>?/"
- for _, r := range s {
- for _, sr := range specials {
- if r == sr {
- return true
- }
- }
- }
- return false
-}
diff --git a/pkg/common/vars.go b/pkg/common/vars.go
deleted file mode 100644
index 2d18650de1d4..000000000000
--- a/pkg/common/vars.go
+++ /dev/null
@@ -1,132 +0,0 @@
-package common
-
-import (
- "path/filepath"
- "strings"
-)
-
-var (
- KB, MB, GB, TB, PB = 1e3, 1e6, 1e9, 1e12, 1e15
- ignoredExtensions = map[string]struct{}{
- // images
- "apng": {},
- "avif": {},
- "avifs": {},
- "bmp": {},
- "dia": {}, // Open-source Visio clone
- "gif": {},
- "icns": {}, // Apple icon image file
- "ico": {}, // Icon file
- "jpg": {},
- "jpeg": {},
- "jxl": {},
- "png": {},
- "svg": {},
- "svgz": {}, // Compressed Scalable Vector Graphics file
- "tga": {},
- "tif": {},
- "tiff": {},
- "vsdx": {}, // Microsoft Visio drawing file
- "vsix": {}, // Visual Studio extension file
-
- // audio
- "fev": {}, // video game audio
- "fsb": {},
- "m2a": {},
- "m4a": {},
- "mp2": {},
- "mp3": {},
- "snag": {},
-
- // video
- "264": {},
- "3gp": {},
- "avi": {},
- "flac": {},
- "flv": {},
- "hdv": {},
- "m4p": {},
- "mov": {},
- "mp4": {},
- "mpg": {},
- "mpeg": {},
- "ogg": {},
- "qt": {},
- "swf": {},
- "vob": {},
- "wav": {},
- "webm": {},
- "webp": {},
- "wmv": {},
-
- // documents
- "pdf": {},
- "psd": {},
-
- // fonts
- "eot": {}, // Embedded OpenType font
- "fnt": {}, // Windows font file
- "fon": {}, // Generic font file
- "otf": {}, // OpenType font
- "ttf": {}, // TrueType font
- "woff": {}, // Web Open Font Format
- "woff2": {}, // Web Open Font Format 2
-
- // misc
- "glb": {}, // 3d models (binary)
- "gltf": {}, // 3d models (JSON/ASCII)
- }
-
- binaryExtensions = map[string]struct{}{
- // binaries
- // These can theoretically contain secrets, but need decoding for users to make sense of them, and we don't have
- // any such decoders right now.
- "class": {}, // Java bytecode class file
- "dll": {}, // Dynamic Link Library, Windows
- "jdo": {}, // Java Data Object, Java serialization format
- "jks": {}, // Java Key Store, Java keystore format
- "ser": {}, // Java serialization format
- "idx": {}, // Index file, often binary
- "hprof": {}, // Java heap dump format
- "exe": {}, // Executable, Windows
- "bin": {}, // Binary, often used for compiled source code
- "so": {}, // Shared object, Unix/Linux
- "o": {}, // Object file from compilation/ intermediate object file
- "a": {}, // Static library, Unix/Linux
- "dylib": {}, // Dynamic library, macOS
- "lib": {}, // Library, Unix/Linux
- "obj": {}, // Object file, typically from compiled source code
- "pdb": {}, // Program Database, Microsoft Visual Studio debugging format
- "dat": {}, // Generic data file, often binary but not always
- "elf": {}, // Executable and Linkable Format, common in Unix/Linux
- "dmg": {}, // Disk Image for macOS
- "iso": {}, // ISO image (optical disk image)
- "img": {}, // Disk image files
- "out": {}, // Common output file from compiled executable in Unix/Linux
- "com": {}, // DOS command file, executable
- "sys": {}, // Windows system file, often a driver
- "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
- "rlib": {}, // Rust library
- "pth": {}, // Pytorch serialized model
- "pbix": {}, // Power BI report file
- "pbit": {}, // Power BI template file
- }
-)
-
-// SkipFile returns true if the file extension is in the ignoredExtensions list.
-func SkipFile(filename string) bool {
- ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), "."))
- _, ok := ignoredExtensions[ext]
- return ok
-}
-
-// IsBinary returns true if the file extension is in the binaryExtensions list.
-func IsBinary(filename string) bool {
- _, ok := binaryExtensions[strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), "."))]
- return ok
-}
diff --git a/pkg/common/vars_test.go b/pkg/common/vars_test.go
deleted file mode 100644
index 1c1e74e34fd8..000000000000
--- a/pkg/common/vars_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package common
-
-import (
- "strings"
- "testing"
-)
-
-func TestSkipFile(t *testing.T) {
- type testCase struct {
- file string
- want bool
- }
-
- // Add a test case for each ignored extension.
- testCases := make([]testCase, 0, (len(ignoredExtensions)+1)*2)
- for ext := range ignoredExtensions {
- testCases = append(testCases, testCase{
- file: "test." + ext,
- want: true,
- })
-
- testCases = append(testCases, testCase{
- file: "test." + strings.ToUpper(ext),
- want: true,
- })
- }
-
- // Add a test case for a file that should not be skipped.
- testCases = append(testCases, testCase{file: "test.txt", want: false})
-
- for _, tt := range testCases {
- t.Run(tt.file, func(t *testing.T) {
- if got := SkipFile(tt.file); got != tt.want {
- t.Errorf("SkipFile(%v) got %v, want %v", tt.file, got, tt.want)
- }
- })
- }
-}
-
-func BenchmarkSkipFile(b *testing.B) {
- for i := 0; i < b.N; i++ {
- SkipFile("test.mp4")
- }
-}
-
-func TestIsBinary(t *testing.T) {
- type testCase struct {
- file string
- want bool
- }
-
- // Add a test case for each binary extension.
- testCases := make([]testCase, 0, len(binaryExtensions)+1)
- for ext := range binaryExtensions {
- testCases = append(testCases, testCase{
- file: "test." + ext,
- want: true,
- })
- }
-
- // Add a test case for a file that should not be skipped.
- testCases = append(testCases, testCase{file: "test.txt", want: false})
-
- for _, tt := range testCases {
- t.Run(tt.file, func(t *testing.T) {
- if got := IsBinary(tt.file); got != tt.want {
- t.Errorf("IsBinary(%v) got %v, want %v", tt.file, got, tt.want)
- }
- })
- }
-}
-
-func BenchmarkIsBinary(b *testing.B) {
- for i := 0; i < b.N; i++ {
- IsBinary("test.exe")
- }
-}
diff --git a/pkg/config/config.go b/pkg/config/config.go
deleted file mode 100644
index 47fe3868c5d8..000000000000
--- a/pkg/config/config.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package config
-
-import (
- "fmt"
- "os"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/custom_detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/configpb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/protoyaml"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/docker"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/filesystem"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/gcs"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/github"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/gitlab"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/jenkins"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/postman"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/s3"
-)
-
-// Config holds user supplied configuration.
-type Config struct {
- Sources []sources.ConfiguredSource
- Detectors []detectors.Detector
-}
-
-// Read parses a given filename into a Config.
-func Read(filename string) (*Config, error) {
- input, err := os.ReadFile(filename)
- if err != nil {
- return nil, err
- }
- return NewYAML(input)
-}
-
-// NewYAML parses the given YAML data into a Config.
-func NewYAML(input []byte) (*Config, error) {
- var inputYAML configpb.Config
- // Parse the raw YAML into a structure.
- if err := protoyaml.UnmarshalStrict(input, &inputYAML); err != nil {
- return nil, err
- }
-
- // Convert to detectors.
- var detectorConfigs []detectors.Detector
- for _, detectorConfig := range inputYAML.Detectors {
- detector, err := custom_detectors.NewWebhookCustomRegex(detectorConfig)
- if err != nil {
- return nil, err
- }
- detectorConfigs = append(detectorConfigs, detector)
- }
-
- // Convert to configured sources.
- var sourceConfigs []sources.ConfiguredSource
- for _, pbSource := range inputYAML.Sources {
- s, err := instantiateSourceFromType(pbSource.GetType())
- if err != nil {
- return nil, err
- }
- src := sources.NewConfiguredSource(s, pbSource)
-
- sourceConfigs = append(sourceConfigs, src)
- }
-
- return &Config{
- Detectors: detectorConfigs,
- Sources: sourceConfigs,
- }, nil
-}
-
-// instantiateSourceFromType creates a concrete implementation of
-// sources.Source for the provided type.
-func instantiateSourceFromType(sourceType string) (sources.Source, error) {
- var source sources.Source
- switch sourceType {
- case sourcespb.SourceType_SOURCE_TYPE_GIT.String():
- source = new(git.Source)
- case sourcespb.SourceType_SOURCE_TYPE_GITHUB.String():
- source = new(github.Source)
- case sourcespb.SourceType_SOURCE_TYPE_GITHUB_UNAUTHENTICATED_ORG.String():
- source = new(github.Source)
- case sourcespb.SourceType_SOURCE_TYPE_PUBLIC_GIT.String():
- source = new(git.Source)
- case sourcespb.SourceType_SOURCE_TYPE_GITLAB.String():
- source = new(gitlab.Source)
- case sourcespb.SourceType_SOURCE_TYPE_POSTMAN.String():
- source = new(postman.Source)
- case sourcespb.SourceType_SOURCE_TYPE_S3.String():
- source = new(s3.Source)
- case sourcespb.SourceType_SOURCE_TYPE_S3_UNAUTHED.String():
- source = new(s3.Source)
- case sourcespb.SourceType_SOURCE_TYPE_FILESYSTEM.String():
- source = new(filesystem.Source)
- case sourcespb.SourceType_SOURCE_TYPE_JENKINS.String():
- source = new(jenkins.Source)
- case sourcespb.SourceType_SOURCE_TYPE_GCS.String():
- source = new(gcs.Source)
- case sourcespb.SourceType_SOURCE_TYPE_GCS_UNAUTHED.String():
- source = new(gcs.Source)
- case sourcespb.SourceType_SOURCE_TYPE_DOCKER.String():
- source = new(docker.Source)
- default:
- return nil, fmt.Errorf("got unexpected source type: %q", sourceType)
- }
-
- return source, nil
-}
diff --git a/pkg/config/detectors.go b/pkg/config/detectors.go
deleted file mode 100644
index d161d79bbddb..000000000000
--- a/pkg/config/detectors.go
+++ /dev/null
@@ -1,231 +0,0 @@
-package config
-
-import (
- "fmt"
- "net/url"
- "sort"
- "strconv"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- dpb "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-var (
- specialGroups = map[string][]DetectorID{
- "all": allDetectors(),
- }
-
- detectorTypeValue = make(map[string]dpb.DetectorType, len(dpb.DetectorType_value))
- validDetectors = make(map[dpb.DetectorType]struct{}, len(dpb.DetectorType_value))
- maxDetectorType dpb.DetectorType
-)
-
-// Setup package local global variables.
-func init() {
- for k, v := range dpb.DetectorType_value {
- dt := dpb.DetectorType(v)
- detectorTypeValue[strings.ToLower(k)] = dt
- validDetectors[dt] = struct{}{}
- if dt > maxDetectorType {
- maxDetectorType = dt
- }
- }
-}
-
-// DetectorID identifies a detector type and version. This struct is used as a
-// way for users to identify detectors, whether unique or not. A DetectorID
-// with Version = 0 indicates all possible versions of a detector.
-type DetectorID struct {
- ID dpb.DetectorType
- Version int
-}
-
-// GetDetectorID extracts the DetectorID from a Detector.
-func GetDetectorID(d detectors.Detector) DetectorID {
- var version int
- if v, ok := d.(detectors.Versioner); ok {
- version = v.Version()
- }
- return DetectorID{
- ID: d.Type(),
- Version: version,
- }
-}
-
-// ParseDetectors parses user supplied string into a list of detectors types.
-// "all" will return the list of all available detectors. The input is comma
-// separated and may use the case-insensitive detector name defined in the
-// protobuf, or the protobuf enum number. A range may be used as well in the
-// form "start-end". Order is preserved and duplicates are ignored.
-func ParseDetectors(input string) ([]DetectorID, error) {
- var output []DetectorID
- seenDetector := map[DetectorID]struct{}{}
- for _, item := range strings.Split(input, ",") {
- item = strings.TrimSpace(item)
- if item == "" {
- continue
- }
- allDetectors, ok := specialGroups[strings.ToLower(item)]
- if !ok {
- var err error
- allDetectors, err = asRange(item)
- if err != nil {
- return nil, err
- }
- }
- for _, d := range allDetectors {
- if _, ok := seenDetector[d]; ok {
- continue
- }
- seenDetector[d] = struct{}{}
- output = append(output, d)
- }
- }
- return output, nil
-}
-
-// ParseDetector parses a user supplied string into a single DetectorID. Input
-// is case-insensitive and either the detector name or ID may be used.
-func ParseDetector(input string) (DetectorID, error) {
- return asDetectorID(strings.TrimSpace(input))
-}
-
-// ParseVerifierEndpoints parses a map of user supplied verifier URLs. The
-// input keys are detector IDs and the values are a comma separated list of
-// URLs. The URLs are validated as HTTPS endpoints.
-func ParseVerifierEndpoints(verifierURLs map[string]string) (map[DetectorID][]string, error) {
- verifiers := make(map[DetectorID][]string, len(verifierURLs))
- for detectorID, urls := range verifierURLs {
- key, err := ParseDetector(detectorID)
- if err != nil {
- return nil, fmt.Errorf("invalid detector ID for verifier: %w", err)
- }
- verifierURLs := strings.Split(urls, ",")
- for i, rawEndpoint := range verifierURLs {
- rawEndpoint := strings.TrimSpace(rawEndpoint)
- verifierURLs[i] = rawEndpoint
- if endpoint, err := url.Parse(rawEndpoint); err != nil {
- return nil, fmt.Errorf("invalid verifier url %q: %w", rawEndpoint, err)
- } else if endpoint.Scheme != "https" {
- return nil, fmt.Errorf("verifier url must be https: %q", rawEndpoint)
- }
- }
- verifiers[key] = append(verifiers[key], verifierURLs...)
- }
- return verifiers, nil
-}
-
-func (id DetectorID) String() string {
- name := dpb.DetectorType_name[int32(id.ID)]
- if name == "" {
- name = ""
- }
- if id.Version == 0 {
- return name
- }
- return fmt.Sprintf("%s.v%d", name, id.Version)
-}
-
-// allDetectors returns an ordered slice of all detector types.
-func allDetectors() []DetectorID {
- all := make([]DetectorID, 0, len(dpb.DetectorType_name))
- for id := range dpb.DetectorType_name {
- all = append(all, DetectorID{ID: dpb.DetectorType(id)})
- }
- sort.Slice(all, func(i, j int) bool { return all[i].ID < all[j].ID })
- return all
-}
-
-// asRange converts a single input into a slice of detector types. If the input
-// is not in range format, a slice of length 1 is returned. Unbounded ranges
-// are allowed.
-func asRange(input string) ([]DetectorID, error) {
- // Check if it's a single detector type.
- dt, err := asDetectorID(input)
- if err == nil {
- return []DetectorID{dt}, nil
- }
-
- // Check if it's a range; if not return the error from above.
- start, end, found := strings.Cut(input, "-")
- if !found {
- return nil, err
- }
- start, end = strings.TrimSpace(start), strings.TrimSpace(end)
-
- // Convert the range start and end to a DetectorType.
- dtStart, err := asDetectorID(start)
- if err != nil {
- return nil, err
- }
- dtEnd, err := asDetectorID(end)
- // If end is empty it's an unbounded range.
- if err != nil && end != "" {
- return nil, err
- }
- if end == "" {
- dtEnd.ID = maxDetectorType
- }
-
- // Ensure these ranges don't have versions.
- if dtEnd.Version != 0 || dtStart.Version != 0 {
- return nil, fmt.Errorf("versions within ranges are not supported: %s", input)
- }
-
- step := dpb.DetectorType(1)
- if dtStart.ID > dtEnd.ID {
- step = -1
- }
- var output []DetectorID
- for dt := dtStart.ID; dt != dtEnd.ID; dt += step {
- if _, ok := validDetectors[dt]; !ok {
- continue
- }
- output = append(output, DetectorID{ID: dt})
- }
- return append(output, dtEnd), nil
-}
-
-// asDetectorID converts the case-insensitive input into a DetectorID. Name or
-// ID may be used. Input is expected to be already trimmed of whitespace.
-func asDetectorID(input string) (DetectorID, error) {
- if input == "" {
- return DetectorID{}, fmt.Errorf("empty detector")
- }
- var detectorID DetectorID
- // Separate the version if there is one.
- if detector, version, hasVersion := strings.Cut(input, "."); hasVersion {
- parsedVersion, err := parseVersion(version)
- if err != nil {
- return DetectorID{}, fmt.Errorf("invalid version for input: %q error: %w", input, err)
- }
- detectorID.Version = parsedVersion
- // Because there was a version, the detector type input is the part before the '.'
- input = detector
- }
-
- // Check if it's a named detector.
- if dt, ok := detectorTypeValue[strings.ToLower(input)]; ok {
- detectorID.ID = dt
- return detectorID, nil
- }
- // Check if it's a detector ID.
- if i, err := strconv.ParseInt(input, 10, 32); err == nil {
- dt := dpb.DetectorType(i)
- if _, ok := validDetectors[dt]; !ok {
- return DetectorID{}, fmt.Errorf("invalid detector ID: %s", input)
- }
- detectorID.ID = dt
- return detectorID, nil
- }
- return DetectorID{}, fmt.Errorf("unrecognized detector type: %s", input)
-}
-
-func parseVersion(v string) (int, error) {
- if !strings.HasPrefix(strings.ToLower(v), "v") {
- return 0, fmt.Errorf("version must start with 'v'")
- }
- version := strings.TrimLeft(v, "vV")
- return strconv.Atoi(version)
-}
diff --git a/pkg/config/detectors_test.go b/pkg/config/detectors_test.go
deleted file mode 100644
index 9b3d2ae76f2e..000000000000
--- a/pkg/config/detectors_test.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package config
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- dpb "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDetectorParsing(t *testing.T) {
- tests := map[string]struct {
- input string
- expected []DetectorID
- }{
- "all": {"AlL", allDetectors()},
- "trailing range": {"0-", allDetectors()},
- "all after 1": {"1-", allDetectors()[1:]},
- "named and valid range": {"aWs,8-9", []DetectorID{{ID: dpb.DetectorType_AWS}, {ID: dpb.DetectorType_Github}, {ID: dpb.DetectorType_Gitlab}}},
- "duplicate order preserved": {"9, 8, 9", []DetectorID{{ID: 9}, {ID: 8}}},
- "named range": {"github - gitlab", []DetectorID{{ID: dpb.DetectorType_Github}, {ID: dpb.DetectorType_Gitlab}}},
- "range preserved": {"8-9, 7-10", []DetectorID{{ID: 8}, {ID: 9}, {ID: 7}, {ID: 10}}},
- "reverse range": {"9-8", []DetectorID{{ID: 9}, {ID: 8}}},
- "range preserved with all": {"10-,all", append(allDetectors()[10:], allDetectors()[:10]...)},
- "empty list item": {"8, ,9", []DetectorID{{ID: 8}, {ID: 9}}},
- "invalid end range": {"0-1337", nil},
- "invalid name": {"foo", nil},
- "negative": {"-1", nil},
- "github.v1": {"github.v1", []DetectorID{{ID: dpb.DetectorType_Github, Version: 1}}},
- "gitlab.v100": {"gitlab.v100", []DetectorID{{ID: dpb.DetectorType_Gitlab, Version: 100}}},
- "range with versions": {"github.v2 - gitlab.v1", nil},
- "invalid version no v": {"gitlab.2", nil},
- "invalid version no number": {"gitlab.github", nil},
- "capital V is fine": {"GiTlAb.V2", []DetectorID{{ID: dpb.DetectorType_Gitlab, Version: 2}}},
- "id number with version": {"8.v2", []DetectorID{{ID: 8, Version: 2}}},
- }
-
- for name, tt := range tests {
- t.Run(name, func(t *testing.T) {
- got, gotErr := ParseDetectors(tt.input)
- if tt.expected == nil {
- assert.Error(t, gotErr)
- return
- }
- assert.Equal(t, tt.expected, got)
- })
- }
-}
diff --git a/pkg/context/context.go b/pkg/context/context.go
deleted file mode 100644
index f761b2539187..000000000000
--- a/pkg/context/context.go
+++ /dev/null
@@ -1,196 +0,0 @@
-package context
-
-import (
- "context"
- "os"
- "time"
-
- "github.com/go-logr/logr"
- "github.com/trufflesecurity/trufflehog/v3/pkg/log"
-)
-
-var (
- // defaultLogger can be set via SetDefaultLogger.
- // It is initialized to write to stderr. To disable, you can call
- // SetDefaultLogger with logr.Discard().
- defaultLogger logr.Logger
-)
-
-// logEntryKey is used to store a reference to the logger in the
-// context.Context key/value bag. This is used for regaining the logger in case
-// the context is converted into a different type.
-const logEntryKey logEntryKeyT = 0
-
-type logEntryKeyT int
-
-func init() {
- defaultLogger, _ = log.New("context", log.WithConsoleSink(os.Stderr))
-}
-
-// Context wraps context.Context and includes an additional Logger() method.
-type Context interface {
- context.Context
- Logger() logr.Logger
-}
-
-// CancelFunc and CancelCauseFunc are type aliases to allow use as if they are
-// the same types as the standard library variants.
-type CancelFunc = context.CancelFunc
-type CancelCauseFunc = context.CancelCauseFunc
-
-// logCtx implements Context.
-type logCtx struct {
- // Embed context.Context to get all methods for free.
- context.Context
- log logr.Logger
-}
-
-// Logger returns a structured logger.
-func (l logCtx) Logger() logr.Logger {
- return l.log
-}
-
-// Background returns context.Background with a default logger.
-func Background() Context {
- return logCtx{
- log: defaultLogger,
- Context: context.Background(),
- }
-}
-
-// TODO returns context.TODO with a default logger.
-func TODO() Context {
- return logCtx{
- log: defaultLogger,
- Context: context.TODO(),
- }
-}
-
-// WithCancel returns context.WithCancel with the log object propagated.
-func WithCancel(parent Context) (Context, context.CancelFunc) {
- ctx, cancel := context.WithCancel(parent)
- lCtx := logCtx{
- log: parent.Logger(),
- Context: ctx,
- }
- return lCtx, cancel
-}
-
-// WithCancelCause returns context.WithCancelCause with the log object propagated.
-func WithCancelCause(parent Context) (Context, context.CancelCauseFunc) {
- ctx, cancel := context.WithCancelCause(parent)
- lCtx := logCtx{
- log: parent.Logger(),
- Context: ctx,
- }
- return lCtx, cancel
-}
-
-// WithDeadline returns context.WithDeadline with the log object propagated and
-// the deadline added to the structured log values.
-func WithDeadline(parent Context, d time.Time) (Context, context.CancelFunc) {
- ctx, cancel := context.WithDeadline(parent, d)
- lCtx := logCtx{
- log: parent.Logger().WithValues("deadline", d),
- Context: ctx,
- }
- return lCtx, cancel
-}
-
-// WithDeadlineCause returns context.WithDeadlineCause with the log object
-// propagated and the deadline added to the structured log values.
-func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, context.CancelFunc) {
- ctx, cancel := context.WithDeadlineCause(parent, d, cause)
- lCtx := logCtx{
- log: parent.Logger().WithValues("deadline", d),
- Context: ctx,
- }
- return lCtx, cancel
-}
-
-// WithTimeout returns context.WithTimeout with the log object propagated and
-// the timeout added to the structured log values.
-func WithTimeout(parent Context, timeout time.Duration) (Context, context.CancelFunc) {
- ctx, cancel := context.WithTimeout(parent, timeout)
- lCtx := logCtx{
- log: parent.Logger().WithValues("timeout", timeout),
- Context: ctx,
- }
- return lCtx, cancel
-}
-
-// WithTimeoutCause returns context.WithTimeoutCause with the log object
-// propagated and the timeout added to the structured log values.
-func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, context.CancelFunc) {
- ctx, cancel := context.WithTimeoutCause(parent, timeout, cause)
- lCtx := logCtx{
- log: parent.Logger().WithValues("timeout", timeout),
- Context: ctx,
- }
- return lCtx, cancel
-}
-
-// Cause returns the context.Cause of the context.
-func Cause(ctx context.Context) error {
- return context.Cause(ctx)
-}
-
-// WithValue returns context.WithValue with the log object propagated and
-// the value added to the structured log values (if the key is a string).
-func WithValue(parent Context, key, val any) Context {
- logger := parent.Logger()
- parentCtx := context.WithValue(parent, key, val)
- if k, ok := key.(string); ok {
- logger = logger.WithValues(k, val)
- parentCtx = context.WithValue(parentCtx, logEntryKey, logger)
- }
- return logCtx{
- log: logger,
- Context: parentCtx,
- }
-}
-
-// WithValues returns context.WithValue with the log object propagated and
-// the values added to the structured log values (if the key is a string).
-func WithValues(parent Context, keyAndVals ...any) Context {
- ctx := parent
- for i := 0; i < len(keyAndVals)-1; i += 2 {
- ctx = WithValue(ctx, keyAndVals[i], keyAndVals[i+1])
- }
- return ctx
-}
-
-// WithLogger converts a context.Context into a Context by adding a logger.
-func WithLogger(parent context.Context, logger logr.Logger) Context {
- return logCtx{
- log: logger,
- Context: context.WithValue(parent, logEntryKey, logger),
- }
-}
-
-// AddLogger converts a context.Context into a Context. If the underlying type
-// is already a Context, that will be returned, otherwise a default logger will
-// be added.
-func AddLogger(parent context.Context) Context {
- // If the context.Context is already a Context, return that.
- if loggerCtx, ok := parent.(Context); ok {
- return loggerCtx
- }
- // If the logger exists in the grab bag (and is the correct type),
- // return that.
- if logEntryVal := parent.Value(logEntryKey); logEntryVal != nil {
- if logger, ok := logEntryVal.(logr.Logger); ok {
- return WithLogger(parent, logger)
- }
- }
- // Otherwise, add the default logger.
- return WithLogger(parent, defaultLogger)
-}
-
-// SetDefaultLogger sets the package-level global default logger that will be
-// used for Background and TODO contexts. On startup, the default logger will
-// be configured to output logs to stderr. Use logr.Discard() to disable all
-// logs from Contexts.
-func SetDefaultLogger(l logr.Logger) {
- defaultLogger = l
-}
diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go
deleted file mode 100644
index d4430dcbabf9..000000000000
--- a/pkg/context/context_test.go
+++ /dev/null
@@ -1,218 +0,0 @@
-package context
-
-import (
- "bytes"
- "context"
- "fmt"
- "strings"
- "testing"
- "time"
-
- "github.com/go-logr/logr"
- "github.com/go-logr/zapr"
- "github.com/stretchr/testify/assert"
- "github.com/trufflesecurity/trufflehog/v3/pkg/log"
- "go.uber.org/zap"
- "go.uber.org/zap/zapcore"
- "go.uber.org/zap/zaptest"
-)
-
-// testLogger is a helper function to create a logger with a closure callback.
-func testLogger(t *testing.T, f func(zapcore.Entry)) logr.Logger {
- return zapr.NewLogger(zaptest.NewLogger(t,
- zaptest.WrapOptions(zap.Hooks(func(e zapcore.Entry) error {
- f(e)
- return nil
- }))))
-}
-
-// infoCounterContext is a helper function to create a Context that will count
-// the number of Info messages logged.
-func infoCounterContext(t *testing.T) (Context, *int) {
- var infoCount int
- logger := testLogger(t, func(e zapcore.Entry) {
- if e.Level == zap.InfoLevel {
- infoCount++
- }
- })
- return WithLogger(context.Background(), logger), &infoCount
-}
-
-func TestWithCancel(t *testing.T) {
- parentCtx, infoCount := infoCounterContext(t)
- ctx, cancel := WithCancel(parentCtx)
- cancel()
- assert.Equal(t, 0, *infoCount)
- select {
- case <-ctx.Done():
- ctx.Logger().Info("yay")
- case <-time.After(1 * time.Second):
- assert.Fail(t, "context should be done")
- }
- assert.Equal(t, 1, *infoCount)
-}
-
-func TestWithTimeout(t *testing.T) {
- parentCtx, infoCount := infoCounterContext(t)
- ctx, cancel := WithTimeout(parentCtx, 10*time.Millisecond)
- defer cancel()
-
- assert.Equal(t, 0, *infoCount)
- select {
- case <-ctx.Done():
- ctx.Logger().Info("yay")
- case <-time.After(1 * time.Second):
- assert.Fail(t, "context should be done")
- }
- assert.Equal(t, 1, *infoCount)
-
- ctx, cancel = WithTimeout(parentCtx, 1*time.Second)
- defer cancel()
- select {
- case <-ctx.Done():
- assert.Fail(t, "context should not be done")
- case <-time.After(10 * time.Millisecond):
- ctx.Logger().Info("yay")
- }
- assert.Equal(t, 2, *infoCount)
-}
-
-func TestWithLogger(t *testing.T) {
- var infoCount int
- logger := testLogger(t, func(e zapcore.Entry) {
- if e.Level == zap.InfoLevel {
- infoCount++
- }
- })
-
- ctx := WithLogger(context.Background(), logger)
- assert.Equal(t, logger, ctx.Logger())
-
- assert.Equal(t, 0, infoCount)
- ctx.Logger().Info("yay")
- assert.Equal(t, 1, infoCount)
-}
-
-func TestAsContext(t *testing.T) {
- var gotValue any
- normalFuncThatTakesContext := func(ctx context.Context) {
- if logCtx, ok := ctx.(Context); ok {
- logCtx.Logger().Info("yay")
- }
- gotValue = ctx.Value("key")
- }
- parentCtx, infoCount := infoCounterContext(t)
- ctx := WithValue(parentCtx, "key", "value")
-
- assert.Equal(t, 0, *infoCount)
- normalFuncThatTakesContext(ctx)
- assert.Equal(t, 1, *infoCount)
- assert.Equal(t, "value", gotValue)
-}
-
-func TestWithValues(t *testing.T) {
- var buffer bytes.Buffer
- logger, sync := log.New("test",
- log.WithConsoleSink(&buffer),
- )
- defer func(prevLogger logr.Logger) {
- defaultLogger = prevLogger
- }(defaultLogger)
- SetDefaultLogger(logger)
-
- {
- ctx1 := Background()
- ctx1.Logger().Info("only a", "a", 0)
-
- ctx2 := WithValue(ctx1, "b", 1)
- ctx2.Logger().Info("only b")
- assert.Equal(t, 1, ctx2.Value("b"))
-
- ctx3 := WithLogger(ctx2, ctx2.Logger().WithValues("c", 2, "d", 3))
- ctx3.Logger().Info("bcd")
-
- ctx2.Logger().Info("only b again")
-
- type customKey string
- ctx4 := WithValue(Background(), customKey("foo"), "bar")
- // foo:bar shouldn't be added to the logger because the key isn't a string
- ctx4.Logger().Info("foo")
-
- ctx5 := WithValues(ctx2, "e", 4, "f", 5, 6, "six")
- ctx5.Logger().Info("bef")
- assert.Equal(t, "six", ctx5.Value(6))
-
- ctx6 := WithValues(ctx2, "what does this do?")
- ctx6.Logger().Info("silently fail I suppose")
- }
- assert.Nil(t, sync())
- logs := strings.Split(strings.TrimSpace(buffer.String()), "\n")
-
- assert.Equal(t, 7, len(logs))
- assert.Contains(t, logs[0], `{"a": 0}`)
- assert.Contains(t, logs[1], `{"b": 1}`)
- assert.Contains(t, logs[2], `{"b": 1, "c": 2, "d": 3}`)
- assert.Contains(t, logs[3], `{"b": 1}`)
- assert.NotContains(t, logs[4], `{"foo": "bar"}`)
- assert.Contains(t, logs[5], `{"b": 1, "e": 4, "f": 5}`)
- assert.Contains(t, logs[6], `silently fail`)
- assert.NotContains(t, logs[6], `what does this do?`)
-}
-
-func TestDefaultLogger(t *testing.T) {
- var panicked bool
- defer func() {
- if r := recover(); r != nil {
- panicked = true
- }
- assert.False(t, panicked)
- }()
- ctx := Background()
- ctx.Logger().Info("this shouldn't panic")
-}
-
-func TestRace(t *testing.T) {
- ctx, cancel := WithCancel(Background())
- go cancel()
- go func() { _ = ctx.Err() }()
- cancel()
- _ = ctx.Err()
-}
-
-func TestCause(t *testing.T) {
- ctx, cancel := WithCancelCause(Background())
- err := fmt.Errorf("oh no")
- cancel(err)
- assert.Equal(t, err, Cause(ctx))
-}
-
-// TestBuriedLogger tests when a logging context is wrapped by a non-logging
-// implementation that we can still regain the original logger.
-func TestBuriedLogger(t *testing.T) {
- var buffer bytes.Buffer
- logger, sync := log.New("test",
- log.WithConsoleSink(&buffer),
- )
- defer func(prevLogger logr.Logger) {
- defaultLogger = prevLogger
- }(defaultLogger)
- SetDefaultLogger(logger)
-
- // Create a context with a key/value log entry.
- ctx := WithValue(Background(), "log", "entry")
- // Convert it to a stdlib context.
- stdCtx := context.WithValue(ctx, "std", "entry") //nolint:staticcheck
- // Try to get the logger back again.
- ctx = AddLogger(stdCtx)
- ctx.Logger().Info("test")
-
- assert.Nil(t, sync())
- logs := strings.Split(strings.TrimSpace(buffer.String()), "\n")
-
- // Logger has the key/value log entry.
- assert.Equal(t, 1, len(logs))
- assert.Contains(t, logs[0], `{"log": "entry"}`)
- // Grab bag still has both values.
- assert.Equal(t, "entry", ctx.Value("log"))
- assert.Equal(t, "entry", ctx.Value("std"))
-}
diff --git a/pkg/custom_detectors/CUSTOM_DETECTORS.md b/pkg/custom_detectors/CUSTOM_DETECTORS.md
deleted file mode 100644
index 4b447cfd5eb4..000000000000
--- a/pkg/custom_detectors/CUSTOM_DETECTORS.md
+++ /dev/null
@@ -1,217 +0,0 @@
-# TruffleHog Custom Detector Setup Guide
-
-This guide will walk you through setting up a custom detector in TruffleHog to identify specific patterns unique to your project. For users of Trufflehog Enterprise, this guide applies to that environment as well.
-
-## Steps to Set Up a Custom Detector
-
-1. **Create a Configuration File**:
- - TruffleHog uses a configuration file, typically named `config.yaml`, to manage custom detector configuration.
- - If this file doesn't exist, create it in your system.
-
-2. **Define the Custom Detector**:
- - Open `config.yaml` with a text editor.
- - Add a new detector under the `detectors` section.
-
- Here's a template for a custom detector:
-
- ```yaml
- # config.yaml
- detectors:
- - name: HogTokenDetector
- keywords:
- - hog
- regex:
- token: '[^A-Za-z0-9+\/]{0,1}([A-Za-z0-9+\/]{40})[^A-Za-z0-9+\/]{0,1}'
- verify:
- - endpoint: http://localhost:8000/
- # 'unsafe' must be set to true if the endpoint uses HTTP
- unsafe: true
- headers:
- - "Authorization: super secret authorization header"
- ```
-
- **Explanation**:
- - **`name`**: A unique identifier for your custom detector.
- - **`keywords`**: An array of strings that, when found, trigger the regex search. If multiple keywords are specified, the presence of any one of them will initiate the regex search.
- - **`regex`**: Defines the patterns to identify potential secrets. You can specify one or more named regular expressions. For a detection to be successful, each named regex must find a match. Capture groups `()` within these regular expressions are used to extract specific portions of the matched text, enabling the detector to process and report on particular segments of the identified patterns.
-
- - **`verify`**: An optional section to validate detected secrets. If you want to verify or unverify detected secrets, this section needs to be configured. If not configured, all detected secrets will be marked as unverified. Read [verification server examples](#verification-server-examples)
-
- **Other allowed parameters:**
- - **`primary_regex_name`**: This parameter allows you designate the primary regex pattern when multiple regex patterns are defined in the regex section. If a match is found, the match for the designated primary regex will be used to determine the line number. The value must be one of the names specified in the regex section.
- - **`exclude_regexes_capture`**: This parameter allows you to define regex patterns to exclude specific parts of a detected secret. If a match is found within the detected secret, the portion matching this regex is excluded from the result.
- - **`exclude_regexes_match`**: This parameter enables you to define regex patterns to exclude entire matches from being reported as secrets. This applies to the entire matched string, not just the token.
- - **`entropy`**: This parameter is used to assess the randomness of detected strings. High entropy often indicates that a string is a potential secret, such as an API key or password, due to its complexity and unpredictability. It helps in filtering false-positives. While an entropy threshold of `3` can be a starting point, it's essential to adjust this value based on your project's specific requirements and the nature of the data you have.
- - **`exclude_words`**: This parameter allows you to specify a list of words that, if present in a detected string, will cause TruffleHog to ignore that string. This is a substring match and does not enforce word boundaries. It applies only to the token.
- - **`validations`**: This parameter lets you define extra validation rules for each regex specified in the regex option. These rules address limitations of Go's RE2 engine, such as the lack of lookahead support, and are applied after a regex match to help reduce false positives.
- Available validation options:
- - **`contains_digit`**: Ensures the match contains at least one numeric digit (0-9). Useful for API keys or tokens that must include numbers.
- - **`contains_lowercase`**: Ensures the match contains at least one lowercase letter (a-z). Common requirement for passwords and mixed-case tokens.
- - **`contains_uppercase`**: Ensures the match contains at least one uppercase letter (A-Z). Helps validate tokens that follow mixed-case conventions.
- - **`contains_special_char`**: Ensures the match contains at least one special character from the set `!@#$%^&*()_+-=[]{}|;:,.<>?`. Useful for complex passwords or encoded tokens.
-
-
- [Here](/examples/generic_with_filters.yml) is an example of a custom detector using these parameters.
-
-3. **Run TruffleHog with the Custom Detector**:
- - Execute TruffleHog, specifying your configuration file:
-
- ```bash
- trufflehog filesystem --config=/config.yaml
- ```
-
- - Replace `` with the path to the directory or file you want to scan, and `` with the path to your `config.yaml`.
- - TruffleHog will scan the specified file or folder using the custom detector you've defined.
-
-4. **Example**:
-
- Let's use the template config provided above to search a file.
-
- Assume you have a file `/tmp/data.txt` with the following content:
-
- ```text
- // this is a custom example
- this file has some random text and maybe a secret
- hog token: pOIAj9x47WT5qElx5JrI3e7O714HgaAIz2ck9sVn
- // end of file
- ```
-
- In this file, the keyword `hog` exists, which will trigger the regex search. The string `pOIAj9x47WT5qElx5JrI3e7O714HgaAIz2ck9sVn` matches the regex pattern, so it should be detected.
-
- Run the following command:
-
- ```bash
- trufflehog filesystem /tmp --config=config.yaml
- ```
-
- The output should be similar to:
-
- ```
- 🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷
-
- Found verified result 🐷🔑
- Detector Type: CustomRegex
- Decoder Type: PLAIN
- Raw result: pOIAj9x47WT5qElx5JrI3e7O714HgaAIz2ck9sVn
- File: /tmp/data.txt
- Line: 3
- ```
-
- The `Raw result` contains the matched string. `File` is the file name where secret was detected and `Line` is the exact line in the file where that was found.
-
-
-## Verification Server Examples
-Unless you run a verification server, secrets found by the custom regex detector will be unverified. Here is an example Python and Go implementation of a verification server for the above config.yaml file.
-
-### Python:
-
-```python
-import json
-from http.server import BaseHTTPRequestHandler, HTTPServer
-
-AUTH_HEADER = 'super secret authorization header'
-
-class Verifier(BaseHTTPRequestHandler):
- def do_GET(self):
- self.send_response(405)
- self.end_headers()
-
- def do_POST(self):
- try:
- if self.headers['Authorization'] != AUTH_HEADER:
- self.send_response(401)
- self.end_headers()
- return
-
- length = int(self.headers['Content-Length'])
- request = json.loads(self.rfile.read(length))
- self.log_message("%s", request)
-
- if not validateTokens(request['HogTokenDetector']['token']):
- self.send_response(200)
- self.end_headers()
- else:
- self.send_response(403)
- self.end_headers()
- except Exception:
- self.send_response(400)
- self.end_headers()
-
-def validateTokens(token):
- return False # Implement actual validation logic
-
-with HTTPServer(('', 8000), Verifier) as server:
- try:
- server.serve_forever()
- except KeyboardInterrupt:
- pass
-```
-
-### Go
-```go
-package main
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "log"
- "net/http"
-)
-
-const authHeader = "super secret authorization header"
-
-type HogTokenDetector struct {
- Token string `json:"token"`
-}
-
-type RequestBody struct {
- HogTokenDetector HogTokenDetector `json:"HogTokenDetector"`
-}
-
-func validateTokens(token string) bool {
- return false // Implement actual validation logic
-}
-
-func verifierHandler(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodPost {
- http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
- return
- }
-
- if r.Header.Get("Authorization") != authHeader {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
-
- body, err := io.ReadAll(r.Body)
- if err != nil {
- http.Error(w, "Bad Request", http.StatusBadRequest)
- return
- }
- defer r.Body.Close()
-
- var requestBody RequestBody
- if err := json.Unmarshal(body, &requestBody); err != nil {
- http.Error(w, "Bad Request", http.StatusBadRequest)
- return
- }
-
- log.Printf("Received Request: %+v", requestBody)
-
- if validateTokens(requestBody.HogTokenDetector.Token) {
- http.Error(w, "Forbidden", http.StatusForbidden)
- } else {
- w.WriteHeader(http.StatusOK)
- }
-}
-
-func main() {
- http.HandleFunc("/", verifierHandler)
- serverAddr := ":8000"
- fmt.Printf("Starting server on %s...\n", serverAddr)
- if err := http.ListenAndServe(serverAddr, nil); err != nil {
- log.Fatalf("Server failed: %s", err)
- }
-}
-```
diff --git a/pkg/custom_detectors/custom_detectors.go b/pkg/custom_detectors/custom_detectors.go
deleted file mode 100644
index 2ba4d89cad40..000000000000
--- a/pkg/custom_detectors/custom_detectors.go
+++ /dev/null
@@ -1,396 +0,0 @@
-package custom_detectors
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "io"
- "maps"
- "net/http"
- "regexp"
- "slices"
- "strings"
-
- "golang.org/x/sync/errgroup"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-// The maximum number of matches from one chunk. This const is used when
-// permutating each regex match to protect the scanner from doing too much work
-// for poorly defined regexps.
-const maxTotalMatches = 100
-
-// CustomRegexWebhook is a CustomRegex with webhook validation that is
-// guaranteed to be valid (assuming the data is not changed after
-// initialization).
-type CustomRegexWebhook struct {
- *custom_detectorspb.CustomRegex
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*CustomRegexWebhook)(nil)
-var _ detectors.CustomFalsePositiveChecker = (*CustomRegexWebhook)(nil)
-var _ detectors.MaxSecretSizeProvider = (*CustomRegexWebhook)(nil)
-
-// NewWebhookCustomRegex initializes and validates a CustomRegexWebhook. An
-// unexported type is intentionally returned here to ensure the values have
-// been validated.
-func NewWebhookCustomRegex(pb *custom_detectorspb.CustomRegex) (*CustomRegexWebhook, error) {
- // TODO: Return all validation errors.
- if err := ValidateKeywords(pb.Keywords); err != nil {
- return nil, err
- }
- if err := ValidateRegex(pb.Regex); err != nil {
- return nil, err
- }
- if err := ValidateRegexSlice(pb.ExcludeRegexesCapture); err != nil {
- return nil, err
- }
- if err := ValidateRegexSlice(pb.ExcludeRegexesMatch); err != nil {
- return nil, err
- }
- if err := ValidatePrimaryRegexName(pb.PrimaryRegexName, pb.Regex); err != nil {
- return nil, err
- }
-
- for _, verify := range pb.Verify {
- if err := ValidateVerifyEndpoint(verify.Endpoint, verify.Unsafe); err != nil {
- return nil, err
- }
- if err := ValidateVerifyHeaders(verify.Headers); err != nil {
- return nil, err
- }
- }
-
- // TODO: Copy only necessary data out of pb.
- return &CustomRegexWebhook{pb}, nil
-}
-
-var httpClient = common.SaneHttpClient()
-
-func (c *CustomRegexWebhook) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- regexMatches := make(map[string][][]string, len(c.GetRegex()))
-
- // Compile exclude regexes targeting the capture group
- excludeRegexesCapture := make([]*regexp.Regexp, 0, len(c.GetExcludeRegexesCapture()))
- for _, exclude := range c.GetExcludeRegexesCapture() {
- regex, err := regexp.Compile(exclude)
- if err != nil {
- // This will only happen if the regex is invalid.
- return nil, err
- }
- excludeRegexesCapture = append(excludeRegexesCapture, regex)
- }
-
- // Compile exclude regexes targeting the entire match
- excludeRegexes := make([]*regexp.Regexp, 0, len(c.GetExcludeRegexesMatch()))
- for _, exclude := range c.GetExcludeRegexesMatch() {
- regex, err := regexp.Compile(exclude)
- if err != nil {
- // This will only happen if the regex is invalid.
- return nil, err
- }
- excludeRegexes = append(excludeRegexes, regex)
- }
-
- // Find all submatches for each regex.
- for name, regex := range c.GetRegex() {
- regex, err := regexp.Compile(regex)
- if err != nil {
- // This will only happen if the regex is invalid.
- return nil, err
- }
- regexMatches[name] = regex.FindAllStringSubmatch(dataStr, -1)
- }
-
- // Permutate each individual match.
- // {
- // "foo": [["match1"]]
- // "bar": [["match2"], ["match3"]]
- // }
- // becomes
- // [
- // {"foo": ["match1"], "bar": ["match2"]},
- // {"foo": ["match1"], "bar": ["match3"]},
- // ]
- matches := permutateMatches(regexMatches)
-
- g := new(errgroup.Group)
-
- // Create result object and test for verification.
- resultsCh := make(chan detectors.Result, maxTotalMatches)
-
-MatchLoop:
- for _, match := range matches {
- for key, values := range match {
- // attempt to use capture group
- secret := values[0]
- if len(values) > 1 {
- secret = values[1]
- }
-
- // check entropy
- entropy := c.GetEntropy()
- if entropy > 0.0 && detectors.StringShannonEntropy(secret) < float64(entropy) {
- continue MatchLoop
- }
-
- // check for exclude words
- for _, excludeWord := range c.GetExcludeWords() {
- if strings.Contains(strings.ToLower(secret), excludeWord) {
- continue MatchLoop
- }
- }
-
- // exclude checks
- for _, excludeMatch := range excludeRegexes {
- if excludeMatch.MatchString(values[0]) {
- continue MatchLoop
- }
- }
-
- // exclude secret (capture group), or if no capture group is set,
- // check against entire match.
- for _, excludeSecret := range excludeRegexesCapture {
- if excludeSecret.MatchString(secret) {
- continue MatchLoop
- }
- }
-
- if validations := c.GetValidations(); validations != nil {
- validationRules := []struct {
- enabled bool
- validator func(string) bool
- }{
- {validations[key].GetContainsDigit(), ContainsDigit},
- {validations[key].GetContainsLowercase(), ContainsLowercase},
- {validations[key].GetContainsUppercase(), ContainsUppercase},
- {validations[key].GetContainsSpecialChar(), ContainsSpecialChar},
- }
-
- for _, rule := range validationRules {
- if rule.enabled && !rule.validator(secret) {
- // skip this match if a validation rule is enabled but missing from the secret
- continue MatchLoop
- }
- }
- }
-
- }
-
- g.Go(func() error {
- return c.createResults(ctx, match, verify, resultsCh)
- })
- }
-
- // Ignore any errors and collect as many of the results as we can.
- _ = g.Wait()
- close(resultsCh)
-
- for result := range resultsCh {
- if result.ExtraData != nil {
- result.ExtraData["name"] = c.GetName()
- }
-
- results = append(results, result)
- }
-
- return results, nil
-}
-
-func (c *CustomRegexWebhook) IsFalsePositive(_ detectors.Result) (bool, string) {
- return false, ""
-}
-
-// custom max size for custom detector
-func (c *CustomRegexWebhook) MaxSecretSize() int64 {
- return 1000
-}
-
-func (c *CustomRegexWebhook) createResults(ctx context.Context, match map[string][]string, verify bool, results chan<- detectors.Result) error {
- if common.IsDone(ctx) {
- // TODO: Log we're possibly leaving out results.
- return ctx.Err()
- }
-
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_CustomRegex,
- DetectorName: c.GetName(),
- ExtraData: map[string]string{},
- }
-
- var raw string
- for _, key := range slices.Sorted(maps.Keys(match)) {
- values := match[key]
- // values[0] contains the entire regex match.
- secret := values[0]
- if len(values) > 1 {
- secret = values[1]
- }
- raw += secret
-
- // if the match is of the primary regex, set it's value as primary secret value in result
- if c.PrimaryRegexName == key {
- result.SetPrimarySecretValue(secret)
- }
- }
-
- result.Raw = []byte(raw)
-
- if !verify {
- select {
- case <-ctx.Done():
- return ctx.Err()
- case results <- result:
- return nil
- }
- }
- // Verify via webhook.
- jsonBody, err := json.Marshal(map[string]map[string][]string{
- c.GetName(): match,
- })
- if err != nil {
- // This should never happen, but if it does, return nil to not
- // disrupt other verification.
- return nil
- }
- // Try each config until we successfully verify.
- for _, verifyConfig := range c.GetVerify() {
- if common.IsDone(ctx) {
- // TODO: Log we're possibly leaving out results.
- return ctx.Err()
- }
- req, err := http.NewRequestWithContext(ctx, "POST", verifyConfig.GetEndpoint(), bytes.NewReader(jsonBody))
- if err != nil {
- continue
- }
- for _, header := range verifyConfig.GetHeaders() {
- key, value, found := strings.Cut(header, ":")
- if !found {
- // Should be unreachable due to validation.
- continue
- }
- req.Header.Add(key, strings.TrimLeft(value, "\t\n\v\f\r "))
- }
- resp, err := httpClient.Do(req)
- if err != nil {
- continue
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- if resp.StatusCode == http.StatusOK {
- // mark the result as verified
- result.Verified = true
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- continue
- }
-
- // TODO: handle different content-type responses seperatly when implement custom detector configurations
- responseStr := string(body)
- // truncate to 200 characters if response length exceeds 200
- if len(responseStr) > 200 {
- responseStr = responseStr[:200]
- }
-
- // store the processed response in ExtraData
- result.ExtraData["response"] = responseStr
-
- break
- }
- }
-
- select {
- case <-ctx.Done():
- return ctx.Err()
- case results <- result:
- return nil
- }
-}
-
-func (c *CustomRegexWebhook) Keywords() []string {
- return c.GetKeywords()
-}
-
-// productIndices produces a permutation of indices for each length. Example:
-// productIndices(3, 2) -> [[0 0] [1 0] [2 0] [0 1] [1 1] [2 1]]. It returns
-// a slice of length no larger than maxTotalMatches.
-func productIndices(lengths ...int) [][]int {
- count := 1
- for _, l := range lengths {
- count *= l
- }
- if count == 0 {
- return nil
- }
- if count > maxTotalMatches {
- count = maxTotalMatches
- }
-
- results := make([][]int, count)
- for i := 0; i < count; i++ {
- j := 1
- result := make([]int, 0, len(lengths))
- for _, l := range lengths {
- result = append(result, (i/j)%l)
- j *= l
- }
- results[i] = result
- }
- return results
-}
-
-// permutateMatches converts the list of all regex matches into all possible
-// permutations selecting one from each named entry in the map. For example:
-// {"foo": [matchA, matchB], "bar": [matchC]} becomes
-//
-// [{"foo": matchA, "bar": matchC}, {"foo": matchB, "bar": matchC}]
-func permutateMatches(regexMatches map[string][][]string) []map[string][]string {
- // Get a consistent order for names and their matching lengths.
- // The lengths are used in calculating the permutation so order matters.
- names := make([]string, 0, len(regexMatches))
- lengths := make([]int, 0, len(regexMatches))
- for key, value := range regexMatches {
- names = append(names, key)
- lengths = append(lengths, len(value))
- }
-
- // Permutate all the indices for each match. For example, if "foo" has
- // [matchA, matchB] and "bar" has [matchC], we will get indices [0 0] [1 0].
- permutationIndices := productIndices(lengths...)
-
- // Build {"foo": matchA, "bar": matchC} and {"foo": matchB, "bar": matchC}
- // from the indices.
- var matches []map[string][]string
- for _, permutation := range permutationIndices {
- candidate := make(map[string][]string, len(permutationIndices))
- for i, name := range names {
- candidate[name] = regexMatches[name][permutation[i]]
- }
- matches = append(matches, candidate)
- }
-
- return matches
-}
-
-func (c *CustomRegexWebhook) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CustomRegex
-}
-
-const defaultDescription = "This is a user-defined detector with no description provided."
-
-func (c *CustomRegexWebhook) Description() string {
- if c.GetDescription() == "" {
- return defaultDescription
- }
- return c.GetDescription()
-}
diff --git a/pkg/custom_detectors/custom_detectors_test.go b/pkg/custom_detectors/custom_detectors_test.go
deleted file mode 100644
index a8290f90f06f..000000000000
--- a/pkg/custom_detectors/custom_detectors_test.go
+++ /dev/null
@@ -1,714 +0,0 @@
-package custom_detectors
-
-import (
- "context"
- "strings"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/stretchr/testify/assert"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/protoyaml"
-)
-
-func TestCustomRegexTemplateParsing(t *testing.T) {
- testCustomRegexTemplateYaml := `name: Internal bi tool
-keywords:
-- secret_v1_
-- pat_v2_
-regex:
- id_pat_example: ([a-zA-Z0-9]{32})
- secret_pat_example: ([a-zA-Z0-9]{32})
-verify:
-- endpoint: http://localhost:8000/{id_pat_example}
- unsafe: true
- headers:
- - 'Authorization: Bearer {secret_pat_example.0}'
- successRanges:
- - 200-250
- - '288'`
-
- var got custom_detectorspb.CustomRegex
- assert.NoError(t, protoyaml.UnmarshalStrict([]byte(testCustomRegexTemplateYaml), &got))
- assert.Equal(t, "Internal bi tool", got.Name)
- assert.Equal(t, []string{"secret_v1_", "pat_v2_"}, got.Keywords)
- assert.Equal(t, map[string]string{
- "id_pat_example": "([a-zA-Z0-9]{32})",
- "secret_pat_example": "([a-zA-Z0-9]{32})",
- }, got.Regex)
- assert.Equal(t, 1, len(got.Verify))
- assert.Equal(t, "http://localhost:8000/{id_pat_example}", got.Verify[0].Endpoint)
- assert.Equal(t, true, got.Verify[0].Unsafe)
- assert.Equal(t, []string{"Authorization: Bearer {secret_pat_example.0}"}, got.Verify[0].Headers)
- assert.Equal(t, []string{"200-250", "288"}, got.Verify[0].SuccessRanges)
-}
-
-func TestCustomRegexWebhookParsing(t *testing.T) {
- testCustomRegexWebhookYaml := `name: Internal bi tool
-keywords:
-- secret_v1_
-- pat_v2_
-regex:
- id_pat_example: ([a-zA-Z0-9]{32})
- secret_pat_example: ([a-zA-Z0-9]{32})
-verify:
-- endpoint: http://localhost:8000/
- unsafe: true
- headers:
- - 'Authorization: Bearer token'`
-
- var got custom_detectorspb.CustomRegex
- assert.NoError(t, protoyaml.UnmarshalStrict([]byte(testCustomRegexWebhookYaml), &got))
- assert.Equal(t, "Internal bi tool", got.Name)
- assert.Equal(t, []string{"secret_v1_", "pat_v2_"}, got.Keywords)
- assert.Equal(t, map[string]string{
- "id_pat_example": "([a-zA-Z0-9]{32})",
- "secret_pat_example": "([a-zA-Z0-9]{32})",
- }, got.Regex)
- assert.Equal(t, 1, len(got.Verify))
- assert.Equal(t, "http://localhost:8000/", got.Verify[0].Endpoint)
- assert.Equal(t, true, got.Verify[0].Unsafe)
- assert.Equal(t, []string{"Authorization: Bearer token"}, got.Verify[0].Headers)
-}
-
-// TestCustomDetectorsParsing tests the full `detectors` configuration.
-func TestCustomDetectorsParsing(t *testing.T) {
- // TODO: Support both template and webhook.
- testYamlConfig := `detectors:
-- name: Internal bi tool
- keywords:
- - secret_v1_
- - pat_v2_
- regex:
- id_pat_example: ([a-zA-Z0-9]{32})
- secret_pat_example: ([a-zA-Z0-9]{32})
- verify:
- - endpoint: http://localhost:8000/
- unsafe: true
- headers:
- - 'Authorization: Bearer token'`
-
- var messages custom_detectorspb.CustomDetectors
- assert.NoError(t, protoyaml.UnmarshalStrict([]byte(testYamlConfig), &messages))
- assert.Equal(t, 1, len(messages.Detectors))
-
- got := messages.Detectors[0]
- assert.Equal(t, "Internal bi tool", got.Name)
- assert.Equal(t, []string{"secret_v1_", "pat_v2_"}, got.Keywords)
- assert.Equal(t, map[string]string{
- "id_pat_example": "([a-zA-Z0-9]{32})",
- "secret_pat_example": "([a-zA-Z0-9]{32})",
- }, got.Regex)
- assert.Equal(t, 1, len(got.Verify))
- assert.Equal(t, "http://localhost:8000/", got.Verify[0].Endpoint)
- assert.Equal(t, true, got.Verify[0].Unsafe)
- assert.Equal(t, []string{"Authorization: Bearer token"}, got.Verify[0].Headers)
-}
-
-func TestFromData_InvalidRegEx(t *testing.T) {
- c := &CustomRegexWebhook{
- &custom_detectorspb.CustomRegex{
- Name: "Internal bi tool",
- Keywords: []string{"secret_v1_", "pat_v2_"},
- Regex: map[string]string{
- "test": "!!?(?:?)[a-zA-Z0-9]{32}", // invalid regex
- },
- },
- }
-
- _, err := c.FromData(context.Background(), false, []byte("test"))
- assert.Error(t, err)
-}
-
-func TestProductIndices(t *testing.T) {
- tests := []struct {
- name string
- input []int
- want [][]int
- }{
- {
- name: "zero",
- input: []int{3, 0},
- want: nil,
- },
- {
- name: "one input",
- input: []int{3},
- want: [][]int{{0}, {1}, {2}},
- },
- {
- name: "two inputs",
- input: []int{3, 2},
- want: [][]int{
- {0, 0}, {1, 0}, {2, 0},
- {0, 1}, {1, 1}, {2, 1},
- },
- },
- {
- name: "three inputs",
- input: []int{3, 2, 3},
- want: [][]int{
- {0, 0, 0}, {1, 0, 0}, {2, 0, 0},
- {0, 1, 0}, {1, 1, 0}, {2, 1, 0},
- {0, 0, 1}, {1, 0, 1}, {2, 0, 1},
- {0, 1, 1}, {1, 1, 1}, {2, 1, 1},
- {0, 0, 2}, {1, 0, 2}, {2, 0, 2},
- {0, 1, 2}, {1, 1, 2}, {2, 1, 2},
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := productIndices(tt.input...)
- assert.Equal(t, tt.want, got)
- })
- }
-}
-
-func TestProductIndicesMax(t *testing.T) {
- got := productIndices(2, 3, 4, 5, 6)
- assert.GreaterOrEqual(t, 2*3*4*5*6, maxTotalMatches)
- assert.Equal(t, maxTotalMatches, len(got))
-}
-
-func TestPermutateMatches(t *testing.T) {
- tests := []struct {
- name string
- input map[string][][]string
- want []map[string][]string
- }{
- {
- name: "two matches",
- input: map[string][][]string{"foo": {{"matchA"}, {"matchB"}}, "bar": {{"matchC"}}},
- want: []map[string][]string{
- {"foo": {"matchA"}, "bar": {"matchC"}},
- {"foo": {"matchB"}, "bar": {"matchC"}},
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := permutateMatches(tt.input)
- assert.Equal(t, tt.want, got)
- })
- }
-}
-
-func TestDetector(t *testing.T) {
- detector, err := NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{
- Name: "test",
- // "password" is normally flagged as a false positive, but CustomRegex
- // should allow the user to decide and report it as a result.
- Keywords: []string{"password"},
- Regex: map[string]string{"regex": "password=\"(.*)\""},
- })
- assert.NoError(t, err)
- results, err := detector.FromData(context.Background(), false, []byte(`password="123456"`))
- assert.NoError(t, err)
- assert.Equal(t, 1, len(results))
- assert.Equal(t, results[0].Raw, []byte(`123456`))
-}
-
-func TestDetectorPrimarySecret(t *testing.T) {
- detector, err := NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"secret"},
- Regex: map[string]string{"id": "id_[A-Z0-9]{10}_yy", "secret": "secret_[A-Z0-9]{10}_yy"},
- PrimaryRegexName: "secret",
- })
- assert.NoError(t, err)
- results, err := detector.FromData(context.Background(), false, []byte(`
- // getData returns id and secret
- func getData()(string, string){
- return "id_ALPHA10100_yy", "secret_YI7C90ACY1_yy"
- }
- `))
- assert.NoError(t, err)
- assert.Equal(t, 1, len(results))
- assert.Equal(t, "secret_YI7C90ACY1_yy", results[0].GetPrimarySecretValue())
-}
-
-func TestDetectorValidations(t *testing.T) {
- type args struct {
- CustomRegex *custom_detectorspb.CustomRegex
- Data string
- }
-
- tests := []struct {
- name string
- input args
- want []detectors.Result
- }{
- {
- name: "custom validation - contains digit",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password"},
- Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "password": {
- ContainsDigit: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: MyStr0ngP@ssword!
- End of file`,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CustomRegex,
- DetectorName: "test",
- Verified: false,
- Raw: []byte("MyStr0ngP@ssword!"),
- },
- },
- },
- {
- name: "custom validation - does not contains digit",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password"},
- Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "password": {
- ContainsDigit: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: MyStrongPassword!
- End of file`,
- },
- want: nil,
- },
- {
- name: "custom validation - contains lowercase",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password"},
- Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "password": {
- ContainsLowercase: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: MyStrongPassword!
- End of file`,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CustomRegex,
- DetectorName: "test",
- Verified: false,
- Raw: []byte("MyStrongPassword!"),
- },
- },
- },
- {
- name: "custom validation - does not contains lowercase",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password"},
- Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "password": {
- ContainsLowercase: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: MYSTRONGPASSWORD!
- End of file`,
- },
- want: nil,
- },
- {
- name: "custom validation - contains uppercase",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password"},
- Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "password": {
- ContainsUppercase: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: MyStrongPassword!
- End of file`,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CustomRegex,
- DetectorName: "test",
- Verified: false,
- Raw: []byte("MyStrongPassword!"),
- },
- },
- },
- {
- name: "custom validation - does not contains uppercase",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password"},
- Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "password": {
- ContainsUppercase: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: mystrongpassword!
- End of file`,
- },
- want: nil,
- },
- {
- name: "custom validation - contains special character",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password"},
- Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "password": {
- ContainsSpecialChar: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: MyStr@ngP@ssword!
- End of file`,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CustomRegex,
- DetectorName: "test",
- Verified: false,
- Raw: []byte("MyStr@ngP@ssword!"),
- },
- },
- },
- {
- name: "custom validation - does not contains special character",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password"},
- Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "password": {
- ContainsSpecialChar: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: MyStrongPassword
- End of file`,
- },
- want: nil,
- },
- {
- name: "custom validation - contains uppercase and special characters",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password"},
- Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "password": {
- ContainsUppercase: true,
- ContainsSpecialChar: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: MyStrongP@ssword
- End of file`,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CustomRegex,
- DetectorName: "test",
- Verified: false,
- Raw: []byte("MyStrongP@ssword"),
- },
- },
- },
- {
- name: "custom validation - contains uppercase but does not contain special characters",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password"},
- Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "password": {
- ContainsUppercase: true,
- ContainsSpecialChar: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: MyStrongPassword
- End of file`,
- },
- want: nil,
- },
- {
- name: "custom validation - wrong regex name in validations",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password"},
- Regex: map[string]string{"password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`},
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "wrong": {
- ContainsUppercase: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: mystrongp@ssword
- End of file`,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CustomRegex,
- DetectorName: "test",
- Verified: false,
- Raw: []byte("mystrongp@ssword"),
- },
- },
- },
- {
- name: "custom validation - multiple regex validations",
- input: args{
- CustomRegex: &custom_detectorspb.CustomRegex{
- Name: "test",
- Keywords: []string{"password", "api_key"},
- Regex: map[string]string{
- "password": `([A-Za-z0-9!@#$%^&*()_+=\-]{12,})`,
- "api_key": `([a-f0-9_-]{32})`,
- },
- Validations: map[string]*custom_detectorspb.ValidationConfig{
- "password": {
- ContainsUppercase: true,
- ContainsSpecialChar: true,
- },
- "api_key": {
- ContainsSpecialChar: true,
- },
- },
- },
- Data: `This is custom example
- This file has a random text and maybe a secret
- Password: MyStrongP@ssword
- API_Key: c392c9837d69b44c764cbf260b-e6184 // should be detected
- API_Key: c392c9837d69b44c764cbf260be6184 // should be filtered by validation
- End of file`,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CustomRegex,
- DetectorName: "test",
- Verified: false,
- Raw: []byte("c392c9837d69b44c764cbf260b-e6184MyStrongP@ssword"),
- },
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- detector, err := NewWebhookCustomRegex(tt.input.CustomRegex)
- assert.NoError(t, err)
- results, err := detector.FromData(context.Background(), false, []byte(tt.input.Data))
- assert.NoError(t, err)
-
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "ExtraData", "verificationError", "primarySecret")
- if diff := cmp.Diff(results, tt.want, ignoreOpts); diff != "" {
- t.Errorf("CustomDetector.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func TestNewWebhookCustomRegex_Validation(t *testing.T) {
- t.Parallel()
-
- // A known-good baseline; each test case mutates exactly one thing to trigger a specific validator.
- base := func() *custom_detectorspb.CustomRegex {
- return &custom_detectorspb.CustomRegex{
- Name: "ok",
- Keywords: []string{"kw"},
- Regex: map[string]string{
- "main": `\btoken_[a-z]+\b`,
- },
- PrimaryRegexName: "main",
- ExcludeRegexesCapture: []string{
- `^skip_.*$`,
- },
- ExcludeRegexesMatch: []string{
- `^ignore_.*$`,
- },
- Verify: []*custom_detectorspb.VerifierConfig{
- {
- Endpoint: "https://example.com/verify",
- Unsafe: false,
- Headers: []string{"Authorization: Bearer x"},
- },
- },
- }
- }
-
- tests := []struct {
- name string
- mutate func(*custom_detectorspb.CustomRegex)
- wantErr bool
- wantErrSubstr string // substring expected in error
- }{
- {
- name: "Validate everything ok",
- mutate: func(pb *custom_detectorspb.CustomRegex) {},
- },
- {
- name: "ValidateKeywords: no keywords",
- mutate: func(pb *custom_detectorspb.CustomRegex) {
- pb.Keywords = nil
- },
- wantErr: true,
- wantErrSubstr: "no keywords",
- },
- {
- name: "ValidateKeywords: empty keyword",
- mutate: func(pb *custom_detectorspb.CustomRegex) {
- pb.Keywords = []string{""}
- },
- wantErr: true,
- wantErrSubstr: "empty keyword",
- },
- {
- name: "ValidateRegex: no regex",
- mutate: func(pb *custom_detectorspb.CustomRegex) {
- pb.Regex = nil
- },
- wantErr: true,
- wantErrSubstr: "no regex",
- },
- {
- name: "ValidateRegex: invalid regex in map",
- mutate: func(pb *custom_detectorspb.CustomRegex) {
- pb.Regex = map[string]string{"main": "("} // invalid
- },
- wantErr: true,
- wantErrSubstr: "regex 'main':",
- },
- {
- name: "ValidateRegexSlice: invalid exclude_regexes_capture",
- mutate: func(pb *custom_detectorspb.CustomRegex) {
- pb.ExcludeRegexesCapture = []string{"("} // invalid
- },
- wantErr: true,
- wantErrSubstr: "regex '1':",
- },
- {
- name: "ValidateRegexSlice: invalid exclude_regexes_match",
- mutate: func(pb *custom_detectorspb.CustomRegex) {
- pb.ExcludeRegexesMatch = []string{"("} // invalid
- },
- wantErr: true,
- wantErrSubstr: "regex '1':",
- },
- {
- name: "ValidatePrimaryRegexName: unknown primary regex name",
- mutate: func(pb *custom_detectorspb.CustomRegex) {
- pb.PrimaryRegexName = "does-not-exist"
- },
- wantErr: true,
- wantErrSubstr: `unknown primary regex name: "does-not-exist"`,
- },
- {
- name: "ValidateVerifyEndpoint: empty endpoint",
- mutate: func(pb *custom_detectorspb.CustomRegex) {
- pb.Verify = []*custom_detectorspb.VerifierConfig{
- {Endpoint: "", Unsafe: false, Headers: []string{"A: b"}},
- }
- },
- wantErr: true,
- wantErrSubstr: "no endpoint",
- },
- {
- name: "ValidateVerifyEndpoint: http endpoint without unsafe=true",
- mutate: func(pb *custom_detectorspb.CustomRegex) {
- pb.Verify = []*custom_detectorspb.VerifierConfig{
- {Endpoint: "http://example.com/verify", Unsafe: false, Headers: []string{"A: b"}},
- }
- },
- wantErr: true,
- wantErrSubstr: "http endpoint must have unsafe=true",
- },
- {
- name: "ValidateVerifyHeaders: header missing colon",
- mutate: func(pb *custom_detectorspb.CustomRegex) {
- pb.Verify = []*custom_detectorspb.VerifierConfig{
- {Endpoint: "https://example.com/verify", Unsafe: false, Headers: []string{"Authorization Bearer x"}},
- }
- },
- wantErr: true,
- wantErrSubstr: `must contain a colon`,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
-
- pb := base()
- tt.mutate(pb)
-
- got, err := NewWebhookCustomRegex(pb)
- if (err != nil) != tt.wantErr {
- t.Fatalf("expected error=%v, got error=%v (result=%#v)", tt.wantErr, err != nil, got)
- }
- if tt.wantErr && got != nil {
- t.Fatalf("expected nil result on error, got=%#v", got)
- }
- if tt.wantErr && !strings.Contains(err.Error(), tt.wantErrSubstr) {
- t.Fatalf("error mismatch:\n got: %q\n want substring: %q", err.Error(), tt.wantErrSubstr)
- }
- })
- }
-}
-
-func BenchmarkProductIndices(b *testing.B) {
- for i := 0; i < b.N; i++ {
- _ = productIndices(3, 2, 6)
- }
-}
diff --git a/pkg/custom_detectors/regex_varstring.go b/pkg/custom_detectors/regex_varstring.go
deleted file mode 100644
index e0efc1364183..000000000000
--- a/pkg/custom_detectors/regex_varstring.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package custom_detectors
-
-import (
- "regexp"
- "strconv"
- "strings"
-)
-
-// nameGroupRegex matches `{ name . group }` ignoring any whitespace.
-var nameGroupRegex = regexp.MustCompile(`{\s*([a-zA-Z0-9-_]+)\s*(\.\s*[0-9]*)?\s*}`)
-
-// RegexVarString is a string with embedded {name.group} variables. A name may
-// only contain alphanumeric, hyphen, and underscore characters. Group is
-// optional but if provided it must be a non-negative integer. If the group is
-// omitted it defaults to 0.
-type RegexVarString struct {
- original string
- // map from name to group
- variables map[string]int
-}
-
-func NewRegexVarString(original string) RegexVarString {
- variables := make(map[string]int)
-
- matches := nameGroupRegex.FindAllStringSubmatch(original, -1)
- for _, match := range matches {
- name, group := match[1], 0
- // The second match will start with a period followed by any number
- // of whitespace.
- if len(match[2]) > 1 {
- g, err := strconv.Atoi(strings.TrimSpace(match[2][1:]))
- if err != nil {
- continue
- }
- group = g
- }
- variables[name] = group
- }
-
- return RegexVarString{
- original: original,
- variables: variables,
- }
-}
diff --git a/pkg/custom_detectors/regex_varstring_test.go b/pkg/custom_detectors/regex_varstring_test.go
deleted file mode 100644
index 7e7bec85b561..000000000000
--- a/pkg/custom_detectors/regex_varstring_test.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package custom_detectors
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestVarString(t *testing.T) {
- tests := []struct {
- name string
- input string
- wantVars map[string]int
- }{
- {
- name: "empty",
- input: "{}",
- wantVars: map[string]int{},
- },
- {
- name: "no subgroup",
- input: "{hello}",
- wantVars: map[string]int{
- "hello": 0,
- },
- },
- {
- name: "with subgroup",
- input: "{hello.123}",
- wantVars: map[string]int{
- "hello": 123,
- },
- },
- {
- name: "subgroup with spaces",
- input: "{\thell0 . 123 }",
- wantVars: map[string]int{
- "hell0": 123,
- },
- },
- {
- name: "multiple groups",
- input: "foo {bar} {bazz.buzz} {buzz.2}",
- wantVars: map[string]int{
- "bar": 0,
- "buzz": 2,
- },
- },
- {
- name: "nested groups",
- input: "{foo {bar}}",
- wantVars: map[string]int{
- "bar": 0,
- },
- },
- {
- name: "decimal without number",
- input: "{foo.}",
- wantVars: map[string]int{
- "foo": 0,
- },
- },
- {
- name: "negative number",
- input: "{foo.-1}",
- wantVars: map[string]int{},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := NewRegexVarString(tt.input)
- assert.Equal(t, tt.input, got.original)
- assert.Equal(t, tt.wantVars, got.variables)
- })
- }
-}
diff --git a/pkg/custom_detectors/validation.go b/pkg/custom_detectors/validation.go
deleted file mode 100644
index b9d3c2f673bf..000000000000
--- a/pkg/custom_detectors/validation.go
+++ /dev/null
@@ -1,173 +0,0 @@
-package custom_detectors
-
-import (
- "fmt"
- "regexp"
- "strconv"
- "strings"
-)
-
-func ValidateKeywords(keywords []string) error {
- if len(keywords) == 0 {
- return fmt.Errorf("no keywords")
- }
-
- for _, keyword := range keywords {
- if len(keyword) == 0 {
- return fmt.Errorf("empty keyword")
- }
- }
- return nil
-}
-
-func ValidateRegex(regex map[string]string) error {
- if len(regex) == 0 {
- return fmt.Errorf("no regex")
- }
- for name, reg := range regex {
- if _, err := regexp.Compile(reg); err != nil {
- return fmt.Errorf("regex '%s': %w", name, err)
- }
- }
- return nil
-}
-
-func ValidateRegexSlice(regex []string) error {
- for i, reg := range regex {
- if _, err := regexp.Compile(reg); err != nil {
- return fmt.Errorf("regex '%d': %w", i+1, err)
- }
- }
- return nil
-}
-
-// validates if a provided non-empty primary regex name exists in the map of regexes
-func ValidatePrimaryRegexName(primaryRegexName string, regexes map[string]string) error {
- if primaryRegexName == "" {
- return nil
- }
- if _, ok := regexes[primaryRegexName]; !ok {
- return fmt.Errorf("unknown primary regex name: %q", primaryRegexName)
- }
- return nil
-}
-
-func ValidateVerifyEndpoint(endpoint string, unsafe bool) error {
- if len(endpoint) == 0 {
- return fmt.Errorf("no endpoint")
- }
-
- if strings.HasPrefix(endpoint, "http://") && !unsafe {
- return fmt.Errorf("http endpoint must have unsafe=true")
- }
- return nil
-}
-
-func ValidateVerifyHeaders(headers []string) error {
- for _, header := range headers {
- if !strings.Contains(header, ":") {
- return fmt.Errorf("header %q must contain a colon", header)
- }
- }
- return nil
-}
-
-func ValidateVerifyRanges(ranges []string) error {
- const httpLowerRange = 100
- const httpUpperRange = 599
-
- for _, successRange := range ranges {
- if !strings.Contains(successRange, "-") {
- httpCode, err := strconv.Atoi(successRange)
- if err != nil {
- return fmt.Errorf("unable to convert http code to int %q", successRange)
- }
-
- if httpCode < httpLowerRange || httpCode > httpUpperRange {
- return fmt.Errorf("invalid http status code %q", successRange)
- }
-
- continue
- }
-
- httpRange := strings.Split(successRange, "-")
- if len(httpRange) != 2 {
- return fmt.Errorf("invalid range format %q", successRange)
- }
-
- lowerBound, err := strconv.Atoi(httpRange[0])
- if err != nil {
- return fmt.Errorf("unable to convert lower bound to int %q", successRange)
- }
-
- upperBound, err := strconv.Atoi(httpRange[1])
- if err != nil {
- return fmt.Errorf("unable to convert upper bound to int %q", successRange)
- }
-
- if lowerBound > upperBound {
- return fmt.Errorf("lower bound greater than upper bound on range %q", successRange)
- }
-
- if lowerBound < httpLowerRange || upperBound > httpUpperRange {
- return fmt.Errorf("invalid http status code range %q", successRange)
- }
- }
- return nil
-}
-
-func ValidateRegexVars(regex map[string]string, body ...string) error {
- for _, b := range body {
- matches := NewRegexVarString(b).variables
- for match := range matches {
- if _, ok := regex[match]; !ok {
- return fmt.Errorf("body %q contains an unknown variable", b)
- }
- }
- }
- return nil
-}
-
-// === Custom Validations ===
-
-// ContainsDigit checks if string contains at least one digit
-func ContainsDigit(s string) bool {
- for i := 0; i < len(s); i++ {
- char := s[i]
- if char >= '0' && char <= '9' {
- return true
- }
- }
-
- return false
-}
-
-// ContainsLowercase checks if string contains at least one lowercase letter
-func ContainsLowercase(s string) bool {
- for i := 0; i < len(s); i++ {
- char := s[i]
- if char >= 'a' && char <= 'z' {
- return true
- }
- }
-
- return false
-}
-
-// ContainsUppercase checks if string contains at least one uppercase letter
-func ContainsUppercase(s string) bool {
- for i := 0; i < len(s); i++ {
- char := s[i]
- if char >= 'A' && char <= 'Z' {
- return true
- }
- }
-
- return false
-}
-
-// ContainsSpecialChar checks if string contains at least one special character
-func ContainsSpecialChar(s string) bool {
- specialChars := "!@#$%^&*()_+-=[]{}|;:,.<>?"
- return strings.ContainsAny(s, specialChars)
-}
diff --git a/pkg/custom_detectors/validation_test.go b/pkg/custom_detectors/validation_test.go
deleted file mode 100644
index 39d74cdd81b6..000000000000
--- a/pkg/custom_detectors/validation_test.go
+++ /dev/null
@@ -1,352 +0,0 @@
-package custom_detectors
-
-import (
- "testing"
-)
-
-func TestCustomDetectorsKeywordValidation(t *testing.T) {
- tests := []struct {
- name string
- input []string
- wantErr bool
- }{
- {
- name: "Test empty list of keywords",
- input: []string{},
- wantErr: true,
- },
- {
- name: "Test empty keyword",
- input: []string{""},
- wantErr: true,
- },
- {
- name: "Test valid keywords",
- input: []string{"hello", "world"},
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := ValidateKeywords(tt.input)
-
- if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
- t.Errorf("ValidateKeywords() error = %v, wantErr %v", got, tt.wantErr)
- }
- })
- }
-}
-
-func TestCustomDetectorsRegexValidation(t *testing.T) {
- tests := []struct {
- name string
- input map[string]string
- wantErr bool
- }{
- {
- name: "Test list of keywords",
- input: map[string]string{
- "id_pat_example": "([a-zA-Z0-9]{32})",
- },
- wantErr: false,
- },
- {
- name: "Test empty list of keywords",
- input: map[string]string{},
- wantErr: true,
- },
- {
- name: "Test invalid regex",
- input: map[string]string{
- "test": "!!?(?:?)[a-zA-Z0-9]{32}",
- },
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := ValidateRegex(tt.input)
-
- if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
- t.Errorf("ValidateRegex() error = %v, wantErr %v", got, tt.wantErr)
- }
- })
- }
-}
-
-func TestCustomDetectorsVerifyEndpointValidation(t *testing.T) {
- tests := []struct {
- name string
- endpoint string
- unsafe bool
- wantErr bool
- }{
- {
- name: "Test http endpoint with unsafe flag",
- endpoint: "http://localhost:8000/{id_pat_example}",
- unsafe: true,
- wantErr: false,
- },
- {
- name: "Test http endpoint without unsafe flag",
- endpoint: "http://localhost:8000/{id_pat_example}",
- unsafe: false,
- wantErr: true,
- },
- {
- name: "Test https endpoint with unsafe flag",
- endpoint: "https://localhost:8000/{id_pat_example}",
- unsafe: true,
- wantErr: false,
- },
- {
- name: "Test https endpoint without unsafe flag",
- endpoint: "https://localhost:8000/{id_pat_example}",
- unsafe: false,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := ValidateVerifyEndpoint(tt.endpoint, tt.unsafe)
-
- if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
- t.Errorf("ValidateVerifyEndpoint() error = %v, wantErr %v", got, tt.wantErr)
- }
- })
- }
-}
-
-func TestCustomDetectorsVerifyHeadersValidation(t *testing.T) {
- tests := []struct {
- name string
- headers []string
- wantErr bool
- }{
- {
- name: "Test single header",
- headers: []string{"Authorization: Bearer {secret_pat_example.0}"},
- wantErr: false,
- },
- {
- name: "Test invalid header",
- headers: []string{"Hello world"},
- wantErr: true,
- },
- {
- name: "Test ugly header",
- headers: []string{"Hello:::::::world::hi:"},
- wantErr: false,
- },
- {
- name: "Test empty header",
- headers: []string{},
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := ValidateVerifyHeaders(tt.headers)
-
- if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
- t.Errorf("ValidateVerifyHeaders() error = %v, wantErr %v", got, tt.wantErr)
- }
- })
- }
-}
-
-func TestCustomDetectorsVerifyRangeValidation(t *testing.T) {
- tests := []struct {
- name string
- ranges []string
- wantErr bool
- }{
- {
- name: "Test multiple mixed ranges",
- ranges: []string{"200", "300-350"},
- wantErr: false,
- },
- {
- name: "Test invalid non-number range",
- ranges: []string{"hi"},
- wantErr: true,
- },
- {
- name: "Test invalid lower to upper range",
- ranges: []string{"200-100"},
- wantErr: true,
- },
- {
- name: "Test invalid http range",
- ranges: []string{"400-1000"},
- wantErr: true,
- },
- {
- name: "Test multiple ranges with invalid inputs",
- ranges: []string{"322", "hello-world", "100-200"},
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := ValidateVerifyRanges(tt.ranges)
-
- if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
- t.Errorf("ValidateVerifyRanges() error = %v, wantErr %v", got, tt.wantErr)
- }
- })
- }
-}
-
-func TestCustomDetectorsVerifyRegexVarsValidation(t *testing.T) {
- tests := []struct {
- name string
- regex map[string]string
- body string
- wantErr bool
- }{
- {
- name: "Regex defined but not used in body",
- regex: map[string]string{"id": "[0-9]{1,10}", "id_pat_example": "([a-zA-Z0-9]{32})"},
- body: "hello world",
- wantErr: false,
- },
- {
- name: "Regex defined and is used in body",
- regex: map[string]string{"id": "[0-9]{1,10}", "id_pat_example": "([a-zA-Z0-9]{32})"},
- body: "hello world {id}",
- wantErr: false,
- },
- {
- name: "Regex var in body but not defined",
- regex: map[string]string{"id": "[0-9]{1,10}", "id_pat_example": "([a-zA-Z0-9]{32})"},
- body: "hello world {hello}",
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := ValidateRegexVars(tt.regex, tt.body)
-
- if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
- t.Errorf("ValidateRegexVars() error = %v, wantErr %v", got, tt.wantErr)
- }
- })
- }
-}
-
-func TestContainsDigit(t *testing.T) {
- type args struct {
- s string
- }
- tests := []struct {
- name string
- args args
- want bool
- }{
- {
- name: "contains digit",
- args: args{s: "lzscqf&60M"},
- want: true,
- },
- {
- name: "does not contains digit",
- args: args{s: "ZlDQOdaM*vsT"},
- want: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := ContainsDigit(tt.args.s); got != tt.want {
- t.Errorf("ContainsDigit() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func TestContainsLowercase(t *testing.T) {
- type args struct {
- s string
- }
- tests := []struct {
- name string
- args args
- want bool
- }{
- {
- name: "contains lower case",
- args: args{s: "g0AJBHdnhRG2"},
- want: true,
- },
- {
- name: "does not contains lower case",
- args: args{s: "V7T#MEA6@+TN"},
- want: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := ContainsLowercase(tt.args.s); got != tt.want {
- t.Errorf("ContainsDigit() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func TestContainsUppercase(t *testing.T) {
- type args struct {
- s string
- }
- tests := []struct {
- name string
- args args
- want bool
- }{
- {
- name: "contains upper case",
- args: args{s: "G1sKkJeKlSQf"},
- want: true,
- },
- {
- name: "does not contains upper case",
- args: args{s: "pq6-14ydz1@d"},
- want: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := ContainsUppercase(tt.args.s); got != tt.want {
- t.Errorf("ContainsDigit() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func TestContainsSpecialChar(t *testing.T) {
- type args struct {
- s string
- }
- tests := []struct {
- name string
- args args
- want bool
- }{
- {
- name: "contains upper case",
- args: args{s: "HP$gE7s=do0B"},
- want: true,
- },
- {
- name: "does not contains upper case",
- args: args{s: "w9gvBYctrSjB"},
- want: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := ContainsSpecialChar(tt.args.s); got != tt.want {
- t.Errorf("ContainsDigit() = %v, want %v", got, tt.want)
- }
- })
- }
-}
diff --git a/pkg/decoders/base64.go b/pkg/decoders/base64.go
deleted file mode 100644
index dcf78cc8a808..000000000000
--- a/pkg/decoders/base64.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package decoders
-
-import (
- "bytes"
- "encoding/base64"
- "unicode"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-type (
- Base64 struct{}
-)
-
-var (
- b64Charset = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/-_=")
- b64EndChars = "+/-_="
- // Given characters are mostly ASCII, we can use a simple array to map.
- b64CharsetMapping [128]bool
-)
-
-func init() {
- // Build an array of all the characters in the base64 charset.
- for _, char := range b64Charset {
- b64CharsetMapping[char] = true
- }
-}
-
-func (d *Base64) Type() detectorspb.DecoderType {
- return detectorspb.DecoderType_BASE64
-}
-
-func (d *Base64) FromChunk(chunk *sources.Chunk) *DecodableChunk {
- decodableChunk := &DecodableChunk{Chunk: chunk, DecoderType: d.Type()}
- encodedSubstrings := getSubstringsOfCharacterSet(chunk.Data, 20, b64CharsetMapping, b64EndChars)
- decodedSubstrings := make(map[string][]byte)
-
- for _, str := range encodedSubstrings {
- dec, err := base64.StdEncoding.DecodeString(str)
- if err == nil && len(dec) > 0 && isASCII(dec) {
- decodedSubstrings[str] = dec
- }
-
- dec, err = base64.RawURLEncoding.DecodeString(str)
- if err == nil && len(dec) > 0 && isASCII(dec) {
- decodedSubstrings[str] = dec
- }
- }
-
- if len(decodedSubstrings) > 0 {
- var result bytes.Buffer
- result.Grow(len(chunk.Data))
-
- start := 0
- for _, encoded := range encodedSubstrings {
- if decoded, ok := decodedSubstrings[encoded]; ok {
- end := bytes.Index(chunk.Data[start:], []byte(encoded))
- if end != -1 {
- result.Write(chunk.Data[start : start+end])
- result.Write(decoded)
- start += end + len(encoded)
- }
- }
- }
- result.Write(chunk.Data[start:])
- chunk.Data = result.Bytes()
- return decodableChunk
- }
-
- return nil
-}
-
-func isASCII(b []byte) bool {
- for i := 0; i < len(b); i++ {
- if b[i] > unicode.MaxASCII {
- return false
- }
- }
- return true
-}
-
-func getSubstringsOfCharacterSet(data []byte, threshold int, charsetMapping [128]bool, endChars string) []string {
- if len(data) == 0 {
- return nil
- }
-
- count := 0
- substringsCount := 0
-
- // Determine the number of substrings that will be returned.
- // Pre-allocate the slice to avoid reallocations.
- for _, char := range data {
- if char < 128 && charsetMapping[char] {
- count++
- } else {
- if count > threshold {
- substringsCount++
- }
- count = 0
- }
- }
- if count > threshold {
- substringsCount++
- }
-
- count = 0
- start := 0
- substrings := make([]string, 0, substringsCount)
-
- for i, char := range data {
- if char < 128 && charsetMapping[char] {
- if count == 0 {
- start = i
- }
- count++
- } else {
- if count > threshold {
- substrings = appendB64Substring(data, start, count, substrings, endChars)
- }
- count = 0
- }
- }
-
- if count > threshold {
- substrings = appendB64Substring(data, start, count, substrings, endChars)
- }
-
- return substrings
-}
-
-func appendB64Substring(data []byte, start, count int, substrings []string, endChars string) []string {
- substring := bytes.TrimLeft(data[start:start+count], endChars)
- if idx := bytes.IndexByte(bytes.TrimRight(substring, endChars), '='); idx != -1 {
- substrings = append(substrings, string(substring[idx+1:]))
- } else {
- substrings = append(substrings, string(substring))
- }
- return substrings
-}
diff --git a/pkg/decoders/base64_test.go b/pkg/decoders/base64_test.go
deleted file mode 100644
index e4a42cef8f68..000000000000
--- a/pkg/decoders/base64_test.go
+++ /dev/null
@@ -1,179 +0,0 @@
-package decoders
-
-import (
- "testing"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-func TestBase64_FromChunk(t *testing.T) {
- tests := []struct {
- chunk *sources.Chunk
- want *sources.Chunk
- name string
- }{
- {
- name: "only b64 chunk",
- chunk: &sources.Chunk{
- Data: []byte(`bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q=`),
- },
- want: &sources.Chunk{
- Data: []byte(`longer-encoded-secret-test`),
- },
- },
- {
- name: "mixed content",
- chunk: &sources.Chunk{
- Data: []byte(`token: bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q=`),
- },
- want: &sources.Chunk{
- Data: []byte(`token: longer-encoded-secret-test`),
- },
- },
- {
- name: "no chunk",
- chunk: &sources.Chunk{
- Data: []byte(``),
- },
- want: nil,
- },
- {
- name: "env var (looks like all b64 decodable but has `=` in the middle)",
- chunk: &sources.Chunk{
- Data: []byte(`some-encoded-secret=dGVzdHNlY3JldA==`),
- },
- want: &sources.Chunk{
- Data: []byte(`some-encoded-secret=testsecret`),
- },
- },
- {
- name: "has longer b64 inside",
- chunk: &sources.Chunk{
- Data: []byte(`some-encoded-secret="bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q="`),
- },
- want: &sources.Chunk{
- Data: []byte(`some-encoded-secret="longer-encoded-secret-test"`),
- },
- },
- {
- name: "many possible substrings",
- chunk: &sources.Chunk{
- Data: []byte(`Many substrings in this slack message could be base64 decoded
- but only dGhpcyBlbmNhcHN1bGF0ZWQgc2VjcmV0 should be decoded.`),
- },
- want: &sources.Chunk{
- Data: []byte(`Many substrings in this slack message could be base64 decoded
- but only this encapsulated secret should be decoded.`),
- },
- },
- {
- name: "b64-url-safe: only b64 chunk",
- chunk: &sources.Chunk{
- Data: []byte(`bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q`),
- },
- want: &sources.Chunk{
- Data: []byte(`longer-encoded-secret-test`),
- },
- },
- {
- name: "b64-url-safe: mixed content",
- chunk: &sources.Chunk{
- Data: []byte(`token: bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q`),
- },
- want: &sources.Chunk{
- Data: []byte(`token: longer-encoded-secret-test`),
- },
- },
- {
- name: "b64-url-safe: env var (looks like all b64 decodable but has `=` in the middle)",
- chunk: &sources.Chunk{
- Data: []byte(`some-encoded-secret=dGVzdHNlY3JldA`),
- },
- want: &sources.Chunk{
- Data: []byte(`some-encoded-secret=testsecret`),
- },
- },
- {
- name: "b64-url-safe: has longer b64 inside",
- chunk: &sources.Chunk{
- Data: []byte(`some-encoded-secret="bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q"`),
- },
- want: &sources.Chunk{
- Data: []byte(`some-encoded-secret="longer-encoded-secret-test"`),
- },
- },
- {
- name: "b64-url-safe: hyphen url b64",
- chunk: &sources.Chunk{
- Data: []byte(`dHJ1ZmZsZWhvZz4-ZmluZHMtc2VjcmV0cw`),
- },
- want: &sources.Chunk{
- Data: []byte(`trufflehog>>finds-secrets`),
- },
- },
- {
- name: "b64-url-safe: underscore url b64",
- chunk: &sources.Chunk{
- Data: []byte(`YjY0dXJsc2FmZS10ZXN0LXNlY3JldC11bmRlcnNjb3Jlcz8_`),
- },
- want: &sources.Chunk{
- Data: []byte(`b64urlsafe-test-secret-underscores??`),
- },
- },
- {
- name: "invalid base64 string",
- chunk: &sources.Chunk{
- Data: []byte(`a3d3fa7c2bb99e469ba55e5834ce79ee4853a8a3`),
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- d := &Base64{}
- got := d.FromChunk(tt.chunk)
- if tt.want != nil {
- if got == nil {
- t.Fatal("got nil, did not want nil")
- }
- if diff := pretty.Compare(string(got.Data), string(tt.want.Data)); diff != "" {
- t.Errorf("Base64FromChunk() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- } else {
- if got != nil {
- t.Error("Expected nil chunk")
- }
- }
- })
- }
-}
-
-func BenchmarkFromChunkSmall(b *testing.B) {
- d := Base64{}
- data := detectors.MustGetBenchmarkData()["small"]
-
- for b.Loop() {
- d.FromChunk(&sources.Chunk{Data: data})
- }
-}
-
-func BenchmarkFromChunkMedium(b *testing.B) {
- d := Base64{}
- data := detectors.MustGetBenchmarkData()["medium"]
-
- for b.Loop() {
- d.FromChunk(&sources.Chunk{Data: data})
- }
-}
-
-func BenchmarkFromChunkLarge(b *testing.B) {
- d := Base64{}
- data := detectors.MustGetBenchmarkData()["big"]
-
- for b.Loop() {
- d.FromChunk(&sources.Chunk{Data: data})
- }
-}
diff --git a/pkg/decoders/decoders.go b/pkg/decoders/decoders.go
deleted file mode 100644
index c49eee403ff2..000000000000
--- a/pkg/decoders/decoders.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package decoders
-
-import (
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-func DefaultDecoders() []Decoder {
- return []Decoder{
- // UTF8 must be first for duplicate detection
- &UTF8{},
- &Base64{},
- &UTF16{},
- &EscapedUnicode{},
- }
-}
-
-// DecodableChunk is a chunk that includes the type of decoder used.
-// This allows us to avoid a type assertion on each decoder.
-type DecodableChunk struct {
- *sources.Chunk
- DecoderType detectorspb.DecoderType
-}
-
-type Decoder interface {
- FromChunk(chunk *sources.Chunk) *DecodableChunk
- Type() detectorspb.DecoderType
-}
-
-// Fuzz is an entrypoint for go-fuzz, which is an AFL-style fuzzing tool.
-// This one attempts to uncover any panics during decoding.
-func Fuzz(data []byte) int {
- decoded := false
- for i, decoder := range DefaultDecoders() {
- // Skip the first decoder (plain), because it will always decode and give
- // priority to the input (return 1).
- if i == 0 {
- continue
- }
- chunk := decoder.FromChunk(&sources.Chunk{Data: data})
- if chunk != nil {
- decoded = true
- }
- }
- if decoded {
- return 1 // prioritize the input
- }
- return -1 // Don't add input to the corpus.
-}
diff --git a/pkg/decoders/escaped_unicode.go b/pkg/decoders/escaped_unicode.go
deleted file mode 100644
index f3f6462b357b..000000000000
--- a/pkg/decoders/escaped_unicode.go
+++ /dev/null
@@ -1,278 +0,0 @@
-package decoders
-
-import (
- "bytes"
- "regexp"
- "strconv"
- "unicode/utf8"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-type EscapedUnicode struct{}
-
-var _ Decoder = (*EscapedUnicode)(nil)
-
-// It might be advantageous to limit these to a subset of acceptable characters, similar to base64.
-// https://dencode.com/en/string/unicode-escape
-var (
- // Standard Unicode notation.
- //https://unicode.org/standard/principles.html
- codePointPat = regexp.MustCompile(`\bU\+([a-fA-F0-9]{4}).?`)
-
- // Common escape sequence used in programming languages.
- escapePat = regexp.MustCompile(`(?i:\\{1,2}u)([a-fA-F0-9]{4})`)
-
- // Additional Unicode escape formats from dencode.com
-
- // \u{X} format - Rust, Swift, some JS, etc. (variable length hex in braces)
- braceEscapePat = regexp.MustCompile(`\\u\{([a-fA-F0-9]{1,6})\}`)
-
- // \U00XXXXXX format - Python, etc. (8-digit format for non-BMP characters)
- longEscapePat = regexp.MustCompile(`\\U([a-fA-F0-9]{8})`)
-
- // \x{X} format - Perl (variable length hex in braces)
- perlEscapePat = regexp.MustCompile(`\\x\{([a-fA-F0-9]{1,6})\}`)
-
- // \X format - CSS (hex without padding). Go's regexp (RE2) has no look-ahead, so we
- // include the delimiter (whitespace, another backslash, or end-of-string) in the
- // match using a non-capturing group. The delimiter is later re-inserted by the
- // decoder when necessary.
- cssEscapePat = regexp.MustCompile(`\\([a-fA-F0-9]{1,6})(?:\s|\\|$)`)
-
- // X; format - HTML/XML (hex with semicolon)
- htmlEscapePat = regexp.MustCompile(`([a-fA-F0-9]{1,6});`)
-
- // %uXXXX format - Percent-encoding (non-standard)
- percentEscapePat = regexp.MustCompile(`%u([a-fA-F0-9]{4})`)
-
- // // 0xX format - Hexadecimal notation with space separation
- // Note: Commenting out for now due to high memory overhead. Review ways to handle this.
- // hexEscapePat = regexp.MustCompile(`0x([a-fA-F0-9]{1,6})(?:\s|$)`)
-)
-
-func (d *EscapedUnicode) Type() detectorspb.DecoderType {
- return detectorspb.DecoderType_ESCAPED_UNICODE
-}
-
-func (d *EscapedUnicode) FromChunk(chunk *sources.Chunk) *DecodableChunk {
- if chunk == nil || len(chunk.Data) == 0 {
- return nil
- }
-
- var (
- // Necessary to avoid data races.
- chunkData = bytes.Clone(chunk.Data)
- matched = false
- )
-
- // Process patterns in priority order - more specific patterns first
- // This prevents conflicts where multiple patterns match the same input
-
- // Long escape format (8 hex digits) - highest priority
- if longEscapePat.Match(chunkData) {
- matched = true
- chunkData = decodeLongEscape(chunkData)
- } else if braceEscapePat.Match(chunkData) {
- matched = true
- chunkData = decodeBraceEscape(chunkData)
- } else if perlEscapePat.Match(chunkData) {
- matched = true
- chunkData = decodePerlEscape(chunkData)
- } else if htmlEscapePat.Match(chunkData) {
- matched = true
- chunkData = decodeHtmlEscape(chunkData)
- } else if percentEscapePat.Match(chunkData) {
- matched = true
- chunkData = decodePercentEscape(chunkData)
- } else if escapePat.Match(chunkData) {
- matched = true
- chunkData = decodeEscaped(chunkData)
- } else if codePointPat.Match(chunkData) {
- matched = true
- chunkData = decodeCodePoint(chunkData)
- } else if cssEscapePat.Match(chunkData) {
- matched = true
- chunkData = decodeCssEscape(chunkData)
- // } else if hexEscapePat.Match(chunkData) {
- // matched = true
- // chunkData = decodeHexEscape(chunkData)
- }
-
- if matched {
- return &DecodableChunk{
- DecoderType: d.Type(),
- Chunk: &sources.Chunk{
- Data: chunkData,
- SourceName: chunk.SourceName,
- SourceID: chunk.SourceID,
- JobID: chunk.JobID,
- SecretID: chunk.SecretID,
- SourceMetadata: chunk.SourceMetadata,
- SourceType: chunk.SourceType,
- Verify: chunk.Verify,
- },
- }
- } else {
- return nil
- }
-}
-
-// Unicode characters are encoded as 1 to 4 bytes per rune.
-const maxBytesPerRune = 4
-const spaceChar = byte(' ')
-
-// decodeWithPattern replaces escape sequences matched by re with their UTF-8
-// equivalents. The regex *must* have the first capturing group contain the
-// hexadecimal code-point digits. Any invalid value (> 0x10FFFF or parse error)
-// is skipped. The replacement walks matches in reverse order to avoid index
-// shifts.
-func decodeWithPattern(input []byte, re *regexp.Regexp) []byte {
- indices := re.FindAllSubmatchIndex(input, -1)
- if len(indices) == 0 {
- return input
- }
-
- utf8Bytes := make([]byte, maxBytesPerRune)
- for i := len(indices) - 1; i >= 0; i-- {
- m := indices[i]
- start, end := m[0], m[1]
- hexStart, hexEnd := m[2], m[3]
-
- cp, err := strconv.ParseUint(string(input[hexStart:hexEnd]), 16, 32)
- if err != nil || cp > 0x10FFFF {
- continue
- }
-
- utf8Len := utf8.EncodeRune(utf8Bytes, rune(cp))
- input = append(input[:start], append(utf8Bytes[:utf8Len], input[end:]...)...)
- }
- return input
-}
-
-func decodeCodePoint(input []byte) []byte {
- // Find all Unicode escape sequences in the input byte slice
- indices := codePointPat.FindAllSubmatchIndex(input, -1)
-
- // Iterate over found indices in reverse order to avoid modifying the slice length
- utf8Bytes := make([]byte, maxBytesPerRune)
- for i := len(indices) - 1; i >= 0; i-- {
- matches := indices[i]
-
- startIndex := matches[0]
- endIndex := matches[1]
- hexStartIndex := matches[2]
- hexEndIndex := matches[3]
-
- // If the input is like `U+1234 U+5678` we should replace `U+1234 `.
- // Otherwise, we should only replace `U+1234`.
- if endIndex != hexEndIndex && input[endIndex-1] != spaceChar {
- endIndex = endIndex - 1
- }
-
- // Extract the hexadecimal value from the escape sequence
- hexValue := string(input[hexStartIndex:hexEndIndex])
-
- // Parse the hexadecimal value to an integer
- unicodeInt, err := strconv.ParseInt(hexValue, 16, 32)
- if err != nil {
- // If there's an error, continue to the next escape sequence
- continue
- }
-
- // Convert the Unicode code point to a UTF-8 representation
- utf8Len := utf8.EncodeRune(utf8Bytes, rune(unicodeInt))
-
- // Replace the escape sequence with the UTF-8 representation
- input = append(input[:startIndex], append(utf8Bytes[:utf8Len], input[endIndex:]...)...)
- }
-
- return input
-}
-
-func decodeEscaped(input []byte) []byte {
- return decodeWithPattern(input, escapePat)
-}
-
-// decodeBraceEscape handles \u{X} format - Rust, Swift, some JS, etc.
-func decodeBraceEscape(input []byte) []byte {
- return decodeWithPattern(input, braceEscapePat)
-}
-
-// decodeLongEscape handles \U00XXXXXX format - Python, etc.
-func decodeLongEscape(input []byte) []byte {
- return decodeWithPattern(input, longEscapePat)
-}
-
-// decodePerlEscape handles \x{X} format - Perl
-func decodePerlEscape(input []byte) []byte {
- return decodeWithPattern(input, perlEscapePat)
-}
-
-// decodeCssEscape handles \X format - CSS (hex without padding, with space delimiter or end of string or next hex sequence)
-func decodeCssEscape(input []byte) []byte {
- return decodeWithPattern(input, cssEscapePat)
-}
-
-// decodeHtmlEscape handles X; format - HTML/XML
-func decodeHtmlEscape(input []byte) []byte {
- return decodeWithPattern(input, htmlEscapePat)
-}
-
-// decodePercentEscape handles %uXXXX format - Percent-encoding (non-standard)
-func decodePercentEscape(input []byte) []byte {
- return decodeWithPattern(input, percentEscapePat)
-}
-
-// decodeHexEscape handles 0xX format - Hexadecimal notation with space separation
-// func decodeHexEscape(input []byte) []byte {
-// // This format requires consecutive 0xNN sequences to be considered for decoding
-// // We'll look for patterns of multiple consecutive hex values
-// hexPattern := regexp.MustCompile(`(?:0x[a-fA-F0-9]{1,2}(?:\s+|$))+`)
-
-// matches := hexPattern.FindAll(input, -1)
-// if len(matches) == 0 {
-// return input
-// }
-
-// result := input
-// for _, match := range matches {
-// // Extract individual hex values
-// individualHex := regexp.MustCompile(`0x([a-fA-F0-9]{1,2})`)
-// hexMatches := individualHex.FindAllSubmatch(match, -1)
-
-// // Only decode if we have multiple consecutive hex values (likely to be a Unicode string)
-// if len(hexMatches) < 3 {
-// continue
-// }
-
-// var decoded []byte
-// for _, hexMatch := range hexMatches {
-// hexValue := string(hexMatch[1])
-// if len(hexValue) == 1 {
-// hexValue = "0" + hexValue // Pad single digit hex values
-// }
-
-// unicodeInt, err := strconv.ParseUint(hexValue, 16, 32)
-// if err != nil || unicodeInt > 0x10FFFF {
-// break
-// }
-
-// if unicodeInt <= 0x7F {
-// // ASCII character
-// decoded = append(decoded, byte(unicodeInt))
-// } else {
-// // Unicode character
-// utf8Bytes := make([]byte, maxBytesPerRune)
-// utf8Len := utf8.EncodeRune(utf8Bytes, rune(unicodeInt))
-// decoded = append(decoded, utf8Bytes[:utf8Len]...)
-// }
-// }
-
-// // Replace the original sequence with decoded bytes
-// result = bytes.Replace(result, match, decoded, 1)
-// }
-
-// return result
-// }
diff --git a/pkg/decoders/escaped_unicode_bench_test.go b/pkg/decoders/escaped_unicode_bench_test.go
deleted file mode 100644
index 752e2056f985..000000000000
--- a/pkg/decoders/escaped_unicode_bench_test.go
+++ /dev/null
@@ -1,228 +0,0 @@
-package decoders
-
-import (
- "testing"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-// Benchmark data for testing
-var (
- // Original formats
- originalUnicodeData = []byte("\\u0041\\u004b\\u0049\\u0041\\u0055\\u004d\\u0034\\u0047\\u0036\\u004f\\u0036\\u004e\\u0041\\u004b\\u0045\\u0037\\u004c\\u0043\\u0044\\u004a")
- codePointData = []byte("U+0041 U+004B U+0049 U+0041 U+0055 U+004D U+0034 U+0047 U+0036 U+004F U+0036 U+004E U+0041 U+004B U+0045 U+0037 U+004C U+0043 U+0044 U+004A")
-
- // New formats
- braceEscapeData = []byte("\\u{41}\\u{4b}\\u{49}\\u{41}\\u{55}\\u{4d}\\u{34}\\u{47}\\u{36}\\u{4f}\\u{36}\\u{4e}\\u{41}\\u{4b}\\u{45}\\u{37}\\u{4c}\\u{43}\\u{44}\\u{4a}")
- longEscapeData = []byte("\\U00000041\\U0000004b\\U00000049\\U00000041\\U00000055\\U0000004d\\U00000034\\U00000047\\U00000036\\U0000004f\\U00000036\\U0000004e\\U00000041\\U0000004b\\U00000045\\U00000037\\U0000004c\\U00000043\\U00000044\\U0000004a")
- perlEscapeData = []byte("\\x{41}\\x{4b}\\x{49}\\x{41}\\x{55}\\x{4d}\\x{34}\\x{47}\\x{36}\\x{4f}\\x{36}\\x{4e}\\x{41}\\x{4b}\\x{45}\\x{37}\\x{4c}\\x{43}\\x{44}\\x{4a}")
- cssEscapeData = []byte("\\41 \\4b \\49 \\41 \\55 \\4d \\34 \\47 \\36 \\4f \\36 \\4e \\41 \\4b \\45 \\37 \\4c \\43 \\44 \\4a ")
- htmlEscapeData = []byte("AKIAUM4G6O6NAKE7LCDJ")
- percentEscapeData = []byte("%u0041%u004b%u0049%u0041%u0055%u004d%u0034%u0047%u0036%u004f%u0036%u004e%u0041%u004b%u0045%u0037%u004c%u0043%u0044%u004a")
- //hexEscapeData = []byte("0x41 0x4b 0x49 0x41 0x55 0x4d 0x34 0x47 0x36 0x4f 0x36 0x4e 0x41 0x4b 0x45 0x37 0x4c 0x43 0x44 0x4a ")
-
- // Mixed content (more realistic scenario)
- mixedContentData = []byte(`
- const config = {
- apiKey: "\\u0041\\u004b\\u0049\\u0041\\u0055\\u004d\\u0034\\u0047\\u0036\\u004f\\u0036\\u004e\\u0041\\u004b\\u0045\\u0037\\u004c\\u0043\\u0044\\u004a",
- secretKey: "\\u{6e}\\u{62}\\u{75}\\u{68}\\u{7a}\\u{4b}\\u{79}\\u{39}\\u{50}\\u{50}\\u{7a}\\u{32}\\u{7a}\\u{47}\\u{33}\\u{47}\\u{54}\\u{4a}\\u{71}\\u{4b}\\u{45}\\u{43}\\u{6e}\\u{71}\\u{4c}\\u{41}\\u{78}\\u{43}\\u{76}\\u{2f}\\u{36}\\u{68}\\u{43}\\u{6a}\\u{6b}\\u{50}\\u{68}\\u{66}\\u{58}\\u{6f}",
- htmlToken: "AKIAUM4G6O6NAKE7LCDJ",
- normalText: "This is normal text that should not be processed"
- }
- `)
-
- // Large data for stress testing
- largeData = func() []byte {
- data := make([]byte, 0, 10000)
- for i := 0; i < 100; i++ {
- data = append(data, originalUnicodeData...)
- data = append(data, braceEscapeData...)
- data = append(data, longEscapeData...)
- data = append(data, htmlEscapeData...)
- data = append(data, []byte(" normal text ")...)
- }
- return data
- }()
-
- // No Unicode data (worst case for performance)
- noUnicodeData = []byte(`
- This is a large block of text with no Unicode escape sequences.
- It contains various programming constructs like:
- - Variable declarations: var x = 123;
- - Function calls: doSomething(param1, param2);
- - Comments: /* this is a comment */
- - Strings: "hello world"
- - Numbers: 42, 3.14159, 0xFF
- - But no Unicode escapes that would trigger our decoders.
- This simulates the common case where files don't contain Unicode escapes.
- `)
-)
-
-// Benchmark individual decoder functions
-func BenchmarkDecodeOriginalEscape(b *testing.B) {
- for b.Loop() {
- _ = decodeEscaped(originalUnicodeData)
- }
-}
-
-func BenchmarkDecodeCodePoint(b *testing.B) {
- for b.Loop() {
- _ = decodeCodePoint(codePointData)
- }
-}
-
-func BenchmarkDecodeBraceEscape(b *testing.B) {
- for b.Loop() {
- _ = decodeBraceEscape(braceEscapeData)
- }
-}
-
-func BenchmarkDecodeLongEscape(b *testing.B) {
- for b.Loop() {
- _ = decodeLongEscape(longEscapeData)
- }
-}
-
-func BenchmarkDecodePerlEscape(b *testing.B) {
- for b.Loop() {
- _ = decodePerlEscape(perlEscapeData)
- }
-}
-
-func BenchmarkDecodeCssEscape(b *testing.B) {
- for b.Loop() {
- _ = decodeCssEscape(cssEscapeData)
- }
-}
-
-func BenchmarkDecodeHtmlEscape(b *testing.B) {
- for b.Loop() {
- _ = decodeHtmlEscape(htmlEscapeData)
- }
-}
-
-func BenchmarkDecodePercentEscape(b *testing.B) {
- for b.Loop() {
- _ = decodePercentEscape(percentEscapeData)
- }
-}
-
-// func BenchmarkDecodeHexEscape(b *testing.B) {
-// for i := 0; i < b.N; i++ {
-// _ = decodeHexEscape(hexEscapeData)
-// }
-// }
-
-// Benchmark the full FromChunk method with different data types
-func BenchmarkFromChunk_OriginalFormat(b *testing.B) {
- decoder := &EscapedUnicode{}
- chunk := &sources.Chunk{Data: originalUnicodeData}
-
- for b.Loop() {
- _ = decoder.FromChunk(chunk)
- }
-}
-
-func BenchmarkFromChunk_BraceFormat(b *testing.B) {
- decoder := &EscapedUnicode{}
- chunk := &sources.Chunk{Data: braceEscapeData}
-
- for b.Loop() {
- _ = decoder.FromChunk(chunk)
- }
-}
-
-func BenchmarkFromChunk_LongFormat(b *testing.B) {
- decoder := &EscapedUnicode{}
- chunk := &sources.Chunk{Data: longEscapeData}
-
- for b.Loop() {
- _ = decoder.FromChunk(chunk)
- }
-}
-
-func BenchmarkFromChunk_HtmlFormat(b *testing.B) {
- decoder := &EscapedUnicode{}
- chunk := &sources.Chunk{Data: htmlEscapeData}
-
- for b.Loop() {
- _ = decoder.FromChunk(chunk)
- }
-}
-
-func BenchmarkFromChunk_MixedContent(b *testing.B) {
- decoder := &EscapedUnicode{}
- chunk := &sources.Chunk{Data: mixedContentData}
-
- for b.Loop() {
- _ = decoder.FromChunk(chunk)
- }
-}
-
-func BenchmarkFromChunk_NoUnicode(b *testing.B) {
- decoder := &EscapedUnicode{}
- chunk := &sources.Chunk{Data: noUnicodeData}
-
- for b.Loop() {
- _ = decoder.FromChunk(chunk)
- }
-}
-
-func BenchmarkFromChunk_LargeData(b *testing.B) {
- decoder := &EscapedUnicode{}
- chunk := &sources.Chunk{Data: largeData}
-
- for b.Loop() {
- _ = decoder.FromChunk(chunk)
- }
-}
-
-// Benchmark regex matching performance (most expensive operation)
-func BenchmarkRegexMatching_AllPatterns(b *testing.B) {
- testData := mixedContentData
-
- for b.Loop() {
- // Simulate the pattern matching in FromChunk
- _ = longEscapePat.Match(testData)
- _ = braceEscapePat.Match(testData)
- _ = perlEscapePat.Match(testData)
- _ = htmlEscapePat.Match(testData)
- _ = percentEscapePat.Match(testData)
- _ = escapePat.Match(testData)
- _ = codePointPat.Match(testData)
- _ = cssEscapePat.Match(testData)
- //_ = hexEscapePat.Match(testData)
- }
-}
-
-func BenchmarkRegexMatching_NoMatch(b *testing.B) {
- testData := noUnicodeData
-
- for b.Loop() {
- // Simulate the pattern matching in FromChunk on data with no matches
- _ = longEscapePat.Match(testData)
- _ = braceEscapePat.Match(testData)
- _ = perlEscapePat.Match(testData)
- _ = htmlEscapePat.Match(testData)
- _ = percentEscapePat.Match(testData)
- _ = escapePat.Match(testData)
- _ = codePointPat.Match(testData)
- _ = cssEscapePat.Match(testData)
- //_ = hexEscapePat.Match(testData)
- }
-}
-
-// Memory allocation benchmarks
-func BenchmarkFromChunk_MemoryAllocation(b *testing.B) {
- decoder := &EscapedUnicode{}
- chunk := &sources.Chunk{Data: mixedContentData}
-
- b.ReportAllocs()
- for b.Loop() {
- result := decoder.FromChunk(chunk)
- if result != nil {
- // Prevent compiler optimization
- _ = result.Data
- }
- }
-}
diff --git a/pkg/decoders/escaped_unicode_test.go b/pkg/decoders/escaped_unicode_test.go
deleted file mode 100644
index fccda71e93d8..000000000000
--- a/pkg/decoders/escaped_unicode_test.go
+++ /dev/null
@@ -1,219 +0,0 @@
-package decoders
-
-import (
- "testing"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-func TestUnicodeEscape_FromChunk(t *testing.T) {
- tests := []struct {
- name string
- chunk *sources.Chunk
- want *sources.Chunk
- wantErr bool
- }{
- // U+1234
- {
- name: "[notation] all escaped",
- chunk: &sources.Chunk{
- Data: []byte("U+0074 U+006f U+006b U+0065 U+006e U+003a U+0020 U+0022 U+0067 U+0068 U+0070 U+005f U+0049 U+0077 U+0064 U+004d U+0078 U+0039 U+0057 U+0046 U+0057 U+0052 U+0052 U+0066 U+004d U+0068 U+0054 U+0059 U+0069 U+0061 U+0056 U+006a U+005a U+0037 U+0038 U+004a U+0066 U+0075 U+0061 U+006d U+0076 U+006e U+0030 U+0059 U+0057 U+0052 U+004d U+0030 U+0022"),
- },
- want: &sources.Chunk{
- Data: []byte("token: \"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\""),
- },
- },
- // \u1234
- {
- name: "[slash] all escaped",
- chunk: &sources.Chunk{
- Data: []byte("\\u0074\\u006f\\u006b\\u0065\\u006e\\u003a\\u0020\\u0022\\u0067\\u0068\\u0070\\u005f\\u0049\\u0077\\u0064\\u004d\\u0078\\u0039\\u0057\\u0046\\u0057\\u0052\\u0052\\u0066\\u004d\\u0068\\u0054\\u0059\\u0069\\u0061\\u0056\\u006a\\u005a\\u0037\\u0038\\u004a\\u0066\\u0075\\u0061\\u006d\\u0076\\u006e\\u0030\\u0059\\u0057\\u0052\\u004d\\u0030\\u0022"),
- },
- want: &sources.Chunk{
- Data: []byte("token: \"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\""),
- },
- },
- {
- name: "[slash] mixed content",
- chunk: &sources.Chunk{
- Data: []byte("npm config set @trufflesec:registry=https://npm.pkg.github.com\nnpm config set //npm.pkg.github.com:_authToken=$'\\u0067hp_9ovSHEBCq0drG42yjoam76iNybtqLN25CgSf'"),
- },
- want: &sources.Chunk{
- Data: []byte("npm config set @trufflesec:registry=https://npm.pkg.github.com\nnpm config set //npm.pkg.github.com:_authToken=$'ghp_9ovSHEBCq0drG42yjoam76iNybtqLN25CgSf'"),
- },
- },
- {
- name: "[slash] multiple slashes",
- chunk: &sources.Chunk{
- Data: []byte(`SameValue("hello","\\u0068el\\u006co"); // true`),
- },
- want: &sources.Chunk{
- Data: []byte(`SameValue("hello","hello"); // true`),
- },
- },
-
- // New test cases for additional Unicode escape formats
-
- // \u{X} format - Rust, Swift, some JS, etc.
- {
- name: "[brace] \\u{X} format - Rust/Swift style",
- chunk: &sources.Chunk{
- Data: []byte("\\u{74}\\u{6f}\\u{6b}\\u{65}\\u{6e}\\u{3a}\\u{20}\\u{22}\\u{67}\\u{68}\\u{70}\\u{5f}\\u{49}\\u{77}\\u{64}\\u{4d}\\u{78}\\u{39}\\u{57}\\u{46}\\u{57}\\u{52}\\u{52}\\u{66}\\u{4d}\\u{68}\\u{54}\\u{59}\\u{69}\\u{61}\\u{56}\\u{6a}\\u{5a}\\u{37}\\u{38}\\u{4a}\\u{66}\\u{75}\\u{61}\\u{6d}\\u{76}\\u{6e}\\u{30}\\u{59}\\u{57}\\u{52}\\u{4d}\\u{30}\\u{22}"),
- },
- want: &sources.Chunk{
- Data: []byte("token: \"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\""),
- },
- },
-
- // \U00XXXXXX format - Python, etc.
- {
- name: "[long] \\U00XXXXXX format - Python style",
- chunk: &sources.Chunk{
- Data: []byte("\\U00000074\\U0000006f\\U0000006b\\U00000065\\U0000006e\\U0000003a\\U00000020\\U00000022\\U00000067\\U00000068\\U00000070\\U0000005f\\U00000049\\U00000077\\U00000064\\U0000004d\\U00000078\\U00000039\\U00000057\\U00000046\\U00000057\\U00000052\\U00000052\\U00000066\\U0000004d\\U00000068\\U00000054\\U00000059\\U00000069\\U00000061\\U00000056\\U0000006a\\U0000005a\\U00000037\\U00000038\\U0000004a\\U00000066\\U00000075\\U00000061\\U0000006d\\U00000076\\U0000006e\\U00000030\\U00000059\\U00000057\\U00000052\\U0000004d\\U00000030\\U00000022"),
- },
- want: &sources.Chunk{
- Data: []byte("token: \"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\""),
- },
- },
-
- // \x{X} format - Perl
- {
- name: "[perl] \\x{X} format - Perl style",
- chunk: &sources.Chunk{
- Data: []byte("\\x{74}\\x{6f}\\x{6b}\\x{65}\\x{6e}\\x{3a}\\x{20}\\x{22}\\x{67}\\x{68}\\x{70}\\x{5f}\\x{49}\\x{77}\\x{64}\\x{4d}\\x{78}\\x{39}\\x{57}\\x{46}\\x{57}\\x{52}\\x{52}\\x{66}\\x{4d}\\x{68}\\x{54}\\x{59}\\x{69}\\x{61}\\x{56}\\x{6a}\\x{5a}\\x{37}\\x{38}\\x{4a}\\x{66}\\x{75}\\x{61}\\x{6d}\\x{76}\\x{6e}\\x{30}\\x{59}\\x{57}\\x{52}\\x{4d}\\x{30}\\x{22}"),
- },
- want: &sources.Chunk{
- Data: []byte("token: \"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\""),
- },
- },
-
- // \X format - CSS (space delimited)
- // ToDo: Look into supporting CSS where there is no whitespace ex: \013322\013171\013001. Currently not supported by this implementation.
- {
- name: "[css] \\X format - CSS style",
- chunk: &sources.Chunk{
- Data: []byte("\\74 \\6f \\6b \\65 \\6e \\3a \\20 \\22 \\67 \\68 \\70 \\5f \\49 \\77 \\64 \\4d \\78 \\39 \\57 \\46 \\57 \\52 \\52 \\66 \\4d \\68 \\54 \\59 \\69 \\61 \\56 \\6a \\5a \\37 \\38 \\4a \\66 \\75 \\61 \\6d \\76 \\6e \\30 \\59 \\57 \\52 \\4d \\30 \\22 "),
- },
- want: &sources.Chunk{
- Data: []byte("token: \"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\""),
- },
- },
-
- // X; format - HTML/XML
- {
- name: "[html] X; format - HTML/XML style",
- chunk: &sources.Chunk{
- Data: []byte("token: "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""),
- },
- want: &sources.Chunk{
- Data: []byte("token: \"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\""),
- },
- },
-
- // %uXXXX format - Percent-encoding (non-standard)
- {
- name: "[percent] %uXXXX format - Percent encoding",
- chunk: &sources.Chunk{
- Data: []byte("%u0074%u006f%u006b%u0065%u006e%u003a%u0020%u0022%u0067%u0068%u0070%u005f%u0049%u0077%u0064%u004d%u0078%u0039%u0057%u0046%u0057%u0052%u0052%u0066%u004d%u0068%u0054%u0059%u0069%u0061%u0056%u006a%u005a%u0037%u0038%u004a%u0066%u0075%u0061%u006d%u0076%u006e%u0030%u0059%u0057%u0052%u004d%u0030%u0022"),
- },
- want: &sources.Chunk{
- Data: []byte("token: \"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\""),
- },
- },
-
- // // 0xX format - Hexadecimal notation with space separation
- // {
- // name: "[hex] 0xX format - Hex with spaces",
- // chunk: &sources.Chunk{
- // Data: []byte("0x74 0x6f 0x6b 0x65 0x6e 0x3a 0x20 0x22 0x67 0x68 0x70 0x5f 0x49 0x77 0x64 0x4d 0x78 0x39 0x57 0x46 0x57 0x52 0x52 0x66 0x4d 0x68 0x54 0x59 0x69 0x61 0x56 0x6a 0x5a 0x37 0x38 0x4a 0x66 0x75 0x61 0x6d 0x76 0x6e 0x30 0x59 0x57 0x52 0x4d 0x30 0x22 "),
- // },
- // want: &sources.Chunk{
- // Data: []byte("token: \"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\""),
- // },
- // },
-
- // // 0xX format - Hexadecimal notation with comma separation
- // {
- // name: "[hex] 0xX format - Hex with commas",
- // chunk: &sources.Chunk{
- // Data: []byte("0x74,0x6f,0x6b,0x65,0x6e,0x3a,0x20,0x22,0x67,0x68,0x70,0x5f,0x49,0x77,0x64,0x4d,0x78,0x39,0x57,0x46,0x57,0x52,0x52,0x66,0x4d,0x68,0x54,0x59,0x69,0x61,0x56,0x6a,0x5a,0x37,0x38,0x4a,0x66,0x75,0x61,0x6d,0x76,0x6e,0x30,0x59,0x57,0x52,0x4d,0x30,0x22"),
- // },
- // want: &sources.Chunk{
- // Data: []byte("token: \"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\""),
- // },
- // },
-
- // Test cases for mixed content with new formats
- {
- name: "[mixed] \\u{X} in code context",
- chunk: &sources.Chunk{
- Data: []byte("const secret = \"\\u{41}\\u{4b}\\u{49}\\u{41}\\u{55}\\u{4d}\\u{34}\\u{47}\\u{36}\\u{4f}\\u{36}\\u{4e}\\u{41}\\u{4b}\\u{45}\\u{37}\\u{4c}\\u{43}\\u{44}\\u{4a}\";"),
- },
- want: &sources.Chunk{
- Data: []byte("const secret = \"AKIAUM4G6O6NAKE7LCDJ\";"),
- },
- },
-
- {
- name: "[mixed] HTML entity in web context",
- chunk: &sources.Chunk{
- Data: []byte("AWS Key: AKIAUM4G6O6NAKE7LCDJ "),
- },
- want: &sources.Chunk{
- Data: []byte("AWS Key: AKIAUM4G6O6NAKE7LCDJ "),
- },
- },
-
- // Test cases for higher Unicode values (non-BMP)
- {
- name: "[emoji] \\u{X} with emoji",
- chunk: &sources.Chunk{
- Data: []byte("\\u{1f600} Happy face emoji"),
- },
- want: &sources.Chunk{
- Data: []byte("😀 Happy face emoji"),
- },
- },
-
- {
- name: "[emoji] \\U00XXXXXX with emoji",
- chunk: &sources.Chunk{
- Data: []byte("\\U0001f600 Happy face emoji"),
- },
- want: &sources.Chunk{
- Data: []byte("😀 Happy face emoji"),
- },
- },
-
- // nothing
- {
- name: "no escaped",
- chunk: &sources.Chunk{
- Data: []byte(`-//npm.fontawesome.com/:_authToken=12345678-2323-1111-1111-12345670B312
-+//npm.fontawesome.com/:_authToken=REMOVED_TOKEN`),
- },
- want: nil,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- d := &EscapedUnicode{}
- got := d.FromChunk(tt.chunk)
- if tt.want != nil {
- if got == nil {
- t.Fatal("got nil, did not want nil")
- }
- if diff := pretty.Compare(string(tt.want.Data), string(got.Data)); diff != "" {
- t.Errorf("UnicodeEscape.FromChunk() %s diff: (-want +got)\n%s", tt.name, diff)
- }
- } else {
- if got != nil {
- t.Error("Expected nil chunk")
- }
- }
- })
- }
-}
diff --git a/pkg/decoders/utf16.go b/pkg/decoders/utf16.go
deleted file mode 100644
index f3da9c3708a4..000000000000
--- a/pkg/decoders/utf16.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package decoders
-
-import (
- "bytes"
- "encoding/binary"
- "unicode/utf8"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-type UTF16 struct{}
-
-func (d *UTF16) Type() detectorspb.DecoderType {
- return detectorspb.DecoderType_UTF16
-}
-
-func (d *UTF16) FromChunk(chunk *sources.Chunk) *DecodableChunk {
- if chunk == nil || len(chunk.Data) == 0 {
- return nil
- }
-
- decodableChunk := &DecodableChunk{Chunk: chunk, DecoderType: d.Type()}
- if utf16Data, err := utf16ToUTF8(chunk.Data); err == nil {
- if len(utf16Data) == 0 {
- return nil
- }
- chunk.Data = utf16Data
- return decodableChunk
- }
-
- return nil
-}
-
-// utf16ToUTF8 converts a byte slice containing UTF-16 encoded data to a UTF-8 encoded byte slice.
-func utf16ToUTF8(b []byte) ([]byte, error) {
- var bufBE, bufLE bytes.Buffer
- for i := 0; i < len(b)-1; i += 2 {
- if r := rune(binary.BigEndian.Uint16(b[i:])); b[i] == 0 && utf8.ValidRune(r) {
- if isPrintableByte(byte(r)) {
- bufBE.WriteRune(r)
- }
- }
- if r := rune(binary.LittleEndian.Uint16(b[i:])); b[i+1] == 0 && utf8.ValidRune(r) {
- if isPrintableByte(byte(r)) {
- bufLE.WriteRune(r)
- }
- }
- }
-
- return append(bufLE.Bytes(), bufBE.Bytes()...), nil
-}
diff --git a/pkg/decoders/utf16_test.dll b/pkg/decoders/utf16_test.dll
deleted file mode 100644
index d5ffcb1110dd..000000000000
Binary files a/pkg/decoders/utf16_test.dll and /dev/null differ
diff --git a/pkg/decoders/utf16_test.go b/pkg/decoders/utf16_test.go
deleted file mode 100644
index ffaed19a5404..000000000000
--- a/pkg/decoders/utf16_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package decoders
-
-import (
- "bytes"
- "os"
- "testing"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-func TestUTF16Decoder(t *testing.T) {
- testCases := []struct {
- name string
- input []byte
- expected []byte
- expectNil bool
- }{
- {
- name: "Valid UTF-16LE input",
- input: []byte{72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0},
- expected: []byte("Hello World"),
- expectNil: false,
- },
- {
- name: "Valid UTF-16BE input",
- input: []byte{0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100},
- expected: []byte("Hello World"),
- expectNil: false,
- },
- {
- name: "Valid UTF-16LE input with BOM (FF FE)",
- input: []byte{255, 254, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0},
- expected: []byte("Hello World"),
- expectNil: false,
- },
- {
- name: "Valid UTF-16BE input with BOM (FE FF)",
- input: []byte{254, 255, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100},
- expected: []byte("Hello World"),
- expectNil: false,
- },
- {
- name: "Invalid UTF-16 input (it's UTF-8)",
- input: []byte("Hello World!"),
- expected: nil,
- expectNil: true,
- },
- {
- name: "Invalid UTF-16 input (odd length)",
- input: []byte{72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 0},
- expected: []byte("Hello Worl"),
- expectNil: false,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- chunk := &sources.Chunk{Data: tc.input}
- decoder := &UTF16{}
- decodedChunk := decoder.FromChunk(chunk)
-
- if tc.expectNil {
- if decodedChunk != nil {
- t.Errorf("Expected nil, got chunk with data: %v", decodedChunk.Data)
- }
- return
- }
- if decodedChunk == nil {
- t.Errorf("Expected chunk with data, got nil")
- return
- }
- if !bytes.Equal(decodedChunk.Data, tc.expected) {
- t.Errorf("Expected decoded data: %s, got: %s", tc.expected, decodedChunk.Data)
- }
- })
- }
-}
-
-func TestDLL(t *testing.T) {
- data, err := os.ReadFile("utf16_test.dll")
- if err != nil {
- t.Errorf("Failed to read test data: %v", err)
- return
- }
-
- chunk := &sources.Chunk{Data: data}
- decoder := &UTF16{}
- decodedChunk := decoder.FromChunk(chunk)
- if decodedChunk == nil {
- t.Errorf("Expected chunk with data, got nil")
- return
- }
- if !bytes.Contains(decodedChunk.Data, []byte("aws_secret_access_key")) {
- t.Errorf("Expected chunk to have aws_secret_access_key")
- return
- }
-}
-
-func BenchmarkUtf16ToUtf8(b *testing.B) {
- // Example UTF-16LE encoded data
- data := []byte{72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0}
-
- for b.Loop() {
- _, _ = utf16ToUTF8(data)
- }
-}
diff --git a/pkg/decoders/utf8.go b/pkg/decoders/utf8.go
deleted file mode 100644
index f8dd847a0708..000000000000
--- a/pkg/decoders/utf8.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package decoders
-
-import (
- "unicode/utf8"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-type UTF8 struct{}
-
-func (d *UTF8) Type() detectorspb.DecoderType {
- return detectorspb.DecoderType_PLAIN
-}
-
-func (d *UTF8) FromChunk(chunk *sources.Chunk) *DecodableChunk {
- if chunk == nil || len(chunk.Data) == 0 {
- return nil
- }
-
- decodableChunk := &DecodableChunk{Chunk: chunk, DecoderType: d.Type()}
-
- if !utf8.Valid(chunk.Data) {
- chunk.Data = extractSubstrings(chunk.Data)
- return decodableChunk
- }
-
- return decodableChunk
-}
-
-// utf8ReplacementBytes holds the UTF-8 encoded form of the Unicode replacement character (U+FFFD).
-// This is pre-computed since it's used frequently when replacing invalid UTF-8 sequences
-// and control characters.
-var utf8ReplacementBytes = []byte(string(utf8.RuneError))
-
-// extractSubstrings sanitizes byte sequences to ensure consistent handling of malformed input
-// while maintaining readable content. It handles ASCII and UTF-8 data as follows:
-//
-// For ASCII range (0-127): preserves printable characters (32-126) while replacing
-// control characters with the UTF-8 replacement character.
-// https://cs.opensource.google/go/go/+/refs/tags/go1.23.3:src/unicode/utf8/utf8.go;l=16
-//
-// For multi-byte sequences: preserves valid UTF-8 as-is, while invalid sequences
-// are replaced with a single UTF-8 replacement character.
-func extractSubstrings(b []byte) []byte {
- dataLen := len(b)
- buf := make([]byte, 0, dataLen)
- for idx := 0; idx < dataLen; {
- // If it's ASCII, handle separately.
- // This is faster than decoding for common cases.
- if b[idx] < utf8.RuneSelf {
- if isPrintableByte(b[idx]) {
- buf = append(buf, b[idx])
- } else {
- buf = append(buf, utf8ReplacementBytes...)
- }
- idx++
- continue
- }
-
- r, size := utf8.DecodeRune(b[idx:])
- if r == utf8.RuneError {
- // Collapse any malformed sequence into a single replacement character
- // rather than replacing each byte individually.
- buf = append(buf, utf8ReplacementBytes...)
- idx++
- } else {
- // Keep valid multi-byte UTF-8 sequences intact to preserve unicode characters.
- buf = append(buf, b[idx:idx+size]...)
- idx += size
- }
- }
-
- return buf
-}
-
-// isPrintableByte reports whether a byte represents a printable ASCII character
-// using a fast byte-range check. This avoids the overhead of utf8.DecodeRune
-// for the common case of ASCII characters (0-127), since we know any byte < 128
-// represents a complete ASCII character and doesn't need UTF-8 decoding.
-// This includes letters, digits, punctuation, and symbols, but excludes control characters.
-// The upper bound is 127 (not 128) because 127 is the DEL control character.
-//
-// https://www.rapidtables.com/code/text/ascii-table.html
-func isPrintableByte(c byte) bool { return c > 31 && c < 127 }
diff --git a/pkg/decoders/utf8_test.go b/pkg/decoders/utf8_test.go
deleted file mode 100644
index 9a605ff24ad9..000000000000
--- a/pkg/decoders/utf8_test.go
+++ /dev/null
@@ -1,381 +0,0 @@
-package decoders
-
-import (
- "strings"
- "testing"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-func TestUTF8_FromChunk_ValidUTF8(t *testing.T) {
- type args struct {
- chunk *sources.Chunk
- }
- tests := []struct {
- name string
- d *UTF8
- args args
- want *sources.Chunk
- wantErr bool
- }{
- {
- name: "successful UTF8 decode",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte("plain 'ol chunk that should decode successfully")},
- },
- want: &sources.Chunk{Data: []byte("plain 'ol chunk that should decode successfully")},
- wantErr: false,
- },
- {
- name: "empty chunk",
- d: &UTF8{},
- args: args{
- chunk: nil,
- },
- want: nil,
- wantErr: false,
- },
- {
- name: "valid UTF8 with control characters",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte("FIRST_KEY_123456\x00SECOND_KEY_789012")},
- },
- want: &sources.Chunk{Data: []byte("FIRST_KEY_123456\x00SECOND_KEY_789012")},
- wantErr: false,
- },
- {
- name: "valid UTF8 with all ASCII control characters",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte{
- 'S', 'T', 'A', 'R', 'T',
- 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
- 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
- 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
- 'E', 'N', 'D',
- }},
- },
- want: &sources.Chunk{Data: []byte("START\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1FEND")},
- wantErr: false,
- },
- {
- name: "aws key in binary data - valid utf8",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte("AWS_ACCESS_KEY_ID\x00\x00\x00AKIAEXAMPLEKEY123\x00")},
- },
- want: &sources.Chunk{Data: []byte("AWS_ACCESS_KEY_ID\x00\x00\x00AKIAEXAMPLEKEY123\x00")},
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- d := &UTF8{}
- got := d.FromChunk(tt.args.chunk)
- if got != nil && tt.want != nil {
- if diff := pretty.Compare(string(got.Data), string(tt.want.Data)); diff != "" {
- t.Errorf("%s: UTF8.FromChunk() diff: (-got +want)\n%s", tt.name, diff)
- }
- } else {
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("%s: UTF8.FromChunk() diff: (-got +want)\n%s", tt.name, diff)
- }
- }
- })
- }
-}
-
-func TestUTF8_FromChunk_InvalidUTF8(t *testing.T) {
- type args struct {
- chunk *sources.Chunk
- }
- tests := []struct {
- name string
- d *UTF8
- args args
- want *sources.Chunk
- wantErr bool
- }{
- {
- name: "basic invalid utf8",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte("\xF0\x28\x8C\x28")},
- },
- want: &sources.Chunk{Data: []byte("�(�(")},
- wantErr: false,
- },
- {
- name: "invalid utf8 between words",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte("START\xF0\x28\x8C\x28MIDDLE\xC0\x80END")},
- },
- want: &sources.Chunk{Data: []byte("START�(�(MIDDLE��END")},
- wantErr: false,
- },
- {
- name: "binary data with embedded text",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte{
- 0xF0, 'S', 'E', 'C', 'R', 'E', 'T', // Invalid UTF-8 before text
- 0xC0, 0x80, // Invalid UTF-8 sequence
- 'V', 'A', 'L', 'U', 'E',
- 0xFF, 0x8C, // More invalid UTF-8
- }},
- },
- want: &sources.Chunk{Data: []byte("�SECRET��VALUE��")},
- wantErr: false,
- },
- {
- name: "binary protocol with length fields",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte{
- 0x02, // frame type
- 0x00, 0x00, 0x00, 0x0A, // length field
- 'P', 'A', 'S', 'S', 'W', 'O', 'R', 'D', '1', '2',
- 0xFE, 0xFF, // checksum
- }},
- },
- want: &sources.Chunk{Data: []byte("�����PASSWORD12��")},
- wantErr: false,
- },
- {
- name: "truncated utf8 sequence",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte("PREFIX\xF0\x28SUFFIX")},
- },
- want: &sources.Chunk{Data: []byte("PREFIX�(SUFFIX")},
- wantErr: false,
- },
- {
- name: "multiple invalid sequences",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte{
- 0xF0, 'A', // Invalid + ASCII
- 0xC0, 0x80, // Invalid sequence
- 'B',
- 0xFF, // Single invalid byte
- 'C',
- 0xF0, 0x28, 0x8C, 0x28, // Invalid sequence
- 'D',
- }},
- },
- want: &sources.Chunk{Data: []byte("�A��B�C�(�(D")},
- wantErr: false,
- },
- {
- name: "invalid utf8 header with embedded secret",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte{
- 0xF0, 0x28, 0x8C, // Invalid UTF-8 sequence
- 'S', 'E', 'C', 'R', 'E', 'T', '=',
- 0xC0, 0x80, // Another invalid UTF-8 sequence
- 'A', 'K', 'I', 'A', '1', '2', '3', '4', '5', '6',
- 0xF8, 0x88, // More invalid UTF-8
- }},
- },
- want: &sources.Chunk{Data: []byte("�(�SECRET=��AKIA123456��")},
- wantErr: false,
- },
- {
- name: "key value pairs with length prefixes",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte{
- 0x00, 0x01, // header
- 'A', 'P', 'I', '_', 'K', 'E', 'Y', '=',
- 0x00, 0x00, 0x00, 0x05, // length
- 'A', 'K', 'I', 'A', '5',
- 0xFF, // separator
- 'S', 'E', 'C', 'R', 'E', 'T', '=',
- 0x00, 0x00, 0x00, 0x06,
- 'S', 'E', 'C', 'R', 'E', 'T',
- }},
- },
- want: &sources.Chunk{Data: []byte("��API_KEY=����AKIA5�SECRET=����SECRET")},
- wantErr: false,
- },
- {
- name: "mixed binary and invalid utf8",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte{
- 0x00, 0x01, // valid binary
- 0xF0, 0x28, // invalid UTF-8
- 'K', 'E', 'Y', '=',
- 0xC0, 0x80, // more invalid UTF-8
- 'V', 'A', 'L', 'U', 'E',
- }},
- },
- want: &sources.Chunk{Data: []byte("���(KEY=��VALUE")},
- wantErr: false,
- },
- {
- name: "very large utf8 sequence",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte(strings.Repeat("世界", 1000))},
- },
- want: &sources.Chunk{Data: []byte(strings.Repeat("世界", 1000))},
- wantErr: false,
- },
- {
- name: "single byte chunk",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte{0x41}}, // Single 'A'
- },
- want: &sources.Chunk{Data: []byte("A")},
- wantErr: false,
- },
- {
- name: "chunk with zero bytes between valid utf8",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte("hello\x00world\x00!")},
- },
- want: &sources.Chunk{Data: []byte("hello\x00world\x00!")},
- wantErr: false,
- },
- {
- name: "multi-byte unicode characters",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte("🌍🌎🌏")},
- },
- want: &sources.Chunk{Data: []byte("🌍🌎🌏")},
- wantErr: false,
- },
- {
- name: "mixed ascii and multi-byte unicode with invalid sequences",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte("Hello 世界\xF0\x28\x8C\x28Testing🌍")},
- },
- want: &sources.Chunk{Data: []byte("Hello 世界�(�(Testing🌍")},
- wantErr: false,
- },
- {
- name: "chunk ending with partial utf8 sequence",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte("Hello\xE2\x80")}, // Incomplete UTF-8 sequence
- },
- want: &sources.Chunk{Data: []byte("Hello��")},
- wantErr: false,
- },
- {
- name: "chunk with all printable ascii chars",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")},
- },
- want: &sources.Chunk{Data: []byte(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")},
- wantErr: false,
- },
- {
- name: "alternating valid and invalid utf8",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte("A\xF0B\xF0C\xF0D")},
- },
- want: &sources.Chunk{Data: []byte("A�B�C�D")},
- wantErr: false,
- },
- {
- name: "overlong utf8 encoding",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte{0xF0, 0x82, 0x82, 0xAC}}, // Overlong encoding of €
- },
- want: &sources.Chunk{Data: []byte("����")},
- wantErr: false,
- },
- {
- name: "utf8 boundary conditions",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte{
- 0xFF, // Invalid single byte -> �
- 0xC2, 0x80, // Minimum valid 2-byte UTF-8 sequence (U+0080) -> \u0080
- 0xDF, 0xBF, // Maximum valid 2-byte UTF-8 sequence (U+07FF) -> ߿
- 0xE0, 0x80, 0x80, // Invalid 3-byte (overlong encoding) -> �
- 0xEF, 0xBF, 0xBF, // Valid 3-byte sequence for U+FFFF -> \uffff
- 0xF0, 0x28, 0x8C, 0x28, // Invalid UTF-8 mixed with ASCII -> �(�(
- 0xF4, 0x8F, 0xBF, 0xBF, // Valid 4-byte sequence for U+10FFFF -> \U0010ffff
- }},
- },
- want: &sources.Chunk{Data: []byte("�\u0080߿���\uffff�(�(\U0010ffff")},
- wantErr: false,
- },
- {
- name: "chunk with byte order mark (BOM)",
- d: &UTF8{},
- args: args{
- chunk: &sources.Chunk{Data: []byte{0xEF, 0xBB, 0xBF, 'h', 'e', 'l', 'l', 'o'}},
- },
- want: &sources.Chunk{Data: []byte("\uFEFFhello")},
- wantErr: false,
- },
- {
- name: "chunk with surrogate pairs",
- d: &UTF8{},
- args: args{
- // Invalid UTF-8 encoding of surrogate pairs
- chunk: &sources.Chunk{Data: []byte{0xED, 0xA0, 0x80, 0xED, 0xB0, 0x80}},
- },
- want: &sources.Chunk{Data: []byte("������")},
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- d := &UTF8{}
- got := d.FromChunk(tt.args.chunk)
- if got != nil && tt.want != nil {
- if diff := pretty.Compare(string(got.Data), string(tt.want.Data)); diff != "" {
- t.Errorf("%s: UTF8.FromChunk() diff: (-got +want)\n%s", tt.name, diff)
- }
- } else {
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("%s: UTF8.FromChunk() diff: (-got +want)\n%s", tt.name, diff)
- }
- }
- })
- }
-}
-
-var testBytes = []byte(`some words with random spaces and
-
-newlines with
-arbitrary length
-of
-
- hey
-
-the lines themselves.
-
-and
-short
-words
-that
-go
-away.`)
-
-func Benchmark_extractSubstrings(b *testing.B) {
- for b.Loop() {
- extractSubstrings(testBytes)
- }
-}
diff --git a/pkg/detectors/abstract/abstract.go b/pkg/detectors/abstract/abstract.go
deleted file mode 100644
index 66c28c1068a7..000000000000
--- a/pkg/detectors/abstract/abstract.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package abstract
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-const abstractURL = "https://exchange-rates.abstractapi.com"
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
-
- 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{"abstract"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"abstract"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Abstract secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Abstract,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyAbstract(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAbstract(ctx context.Context, client *http.Client, resMatch string) (bool, error) {
- // https://docs.abstractapi.com/exchange-rates#response-and-error-codes
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, abstractURL+fmt.Sprintf("/v1/live/?api_key=%s&base=USD", resMatch), nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- // https://docs.abstractapi.com/exchange-rates#response-and-error-codes
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Abstract
-}
-
-func (s Scanner) Description() string {
- return "Abstract API provides various services including exchange rates. The API keys can be used to access these services and retrieve data."
-}
diff --git a/pkg/detectors/abstract/abstract_integration_test.go b/pkg/detectors/abstract/abstract_integration_test.go
deleted file mode 100644
index 2ed3939c9445..000000000000
--- a/pkg/detectors/abstract/abstract_integration_test.go
+++ /dev/null
@@ -1,168 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package abstract
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAbstract_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ABSTRACT")
- inactiveSecret := testSecrets.MustGetField("ABSTRACT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abstract secret %s within but verified", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Abstract,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abstract secret %s within but verified", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Abstract,
- Verified: false,
- }
- r.SetVerificationError(context.DeadlineExceeded)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "{}")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abstract secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Abstract,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abstract secret %s within but not valid", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Abstract,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- time.Sleep(900 * time.Millisecond) // avoid rate limiting
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Abstract.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("Abstract.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/abstract/abstract_test.go b/pkg/detectors/abstract/abstract_test.go
deleted file mode 100644
index 1a1172c50531..000000000000
--- a/pkg/detectors/abstract/abstract_test.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package abstract
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAbstract_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to abstract API
- [DEBUG] Using API_KEY=oxpf4a93fjovt0v1z6lltcbcizlrml98
- [INFO] Response received: 200 OK
- `,
- want: []string{"oxpf4a93fjovt0v1z6lltcbcizlrml98"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {abstract}
- {abstract AQAAABAAA 5422358j60yxo9nc0dbpxby602tsxd6j}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"5422358j60yxo9nc0dbpxby602tsxd6j"},
- },
- {
- name: "valid pattern - two keys",
- input: `
- [INFO] Sending request to abstract API
- [DEBUG] Using API_KEY=oxpf4a93fjovt0v1z6lltcbcizlrml98
- [Error] Response received: 401 UnAuthorized
- [INFO] Sending request to abstract API
- [DEBUG] Using API_KEY=muytrs09876iugt67s7a7sa0akhsxz82
- [INFO] Response received: 200 OK
- `,
- want: []string{"oxpf4a93fjovt0v1z6lltcbcizlrml98", "muytrs09876iugt67s7a7sa0akhsxz82"},
- },
- {
- name: "valid pattern - out of prefix range",
- input: `
- [INFO] Sending request to abstract API
- [INFO] Processing request
- [Info] Response received: 200 OK
- [DEBUG] Used API_KEY=oxpf4a93fjovt0v1z6lltcbcizlrml98
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to abstract API
- [DEBUG] Using API_KEY=zxcvbr12345iugt67s7a7sa0akhsXz820
- [ERROR] Response received: 400 BadRequest
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/abuseipdb/abuseipdb.go b/pkg/detectors/abuseipdb/abuseipdb.go
deleted file mode 100644
index 7ee6c999ec5a..000000000000
--- a/pkg/detectors/abuseipdb/abuseipdb.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package abuseipdb
-
-import (
- "bytes"
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-const abuseipdbURL = "https://api.abuseipdb.com"
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
-
- 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{"abuseipdb"}) + `\b([a-z0-9]{80})\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{"abuseipdb"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify AbuseIPDB secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AbuseIPDB,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyAbuseIPDB(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAbuseIPDB(ctx context.Context, client *http.Client, resMatch string) (bool, error) {
- // https://docs.abuseipdb.com/#check-endpoint
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, abuseipdbURL+"/api/v2/check?ipAddress=8.8.8.8", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Key", resMatch)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- switch res.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
- validResponse := bytes.Contains(bodyBytes, []byte("ipAddress"))
- if validResponse {
- return true, nil
- } else {
- return false, nil
- }
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AbuseIPDB
-}
-
-func (s Scanner) Description() string {
- return "AbuseIPDB is a project dedicated to helping combat the spread of hackers, spammers, and abusive activity on the internet. AbuseIPDB API keys can be used to report and check IP addresses for abusive activities."
-}
diff --git a/pkg/detectors/abuseipdb/abuseipdb_integration_test.go b/pkg/detectors/abuseipdb/abuseipdb_integration_test.go
deleted file mode 100644
index bd481f08059d..000000000000
--- a/pkg/detectors/abuseipdb/abuseipdb_integration_test.go
+++ /dev/null
@@ -1,167 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package abuseipdb
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAbuseIPDB_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ABUSEIPDB")
- inactiveSecret := testSecrets.MustGetField("ABUSEIPDB_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abuseipdb secret %s within but verified", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AbuseIPDB,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abuseipdb secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_AbuseIPDB,
- Verified: false,
- }
- r.SetVerificationError(context.DeadlineExceeded)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "{}")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abuseipdb secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_AbuseIPDB,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abuseipdb secret %s within", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AbuseIPDB,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AbuseIPDB.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("AbuseIPDB.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/abuseipdb/abuseipdb_test.go b/pkg/detectors/abuseipdb/abuseipdb_test.go
deleted file mode 100644
index d0fa8244c12a..000000000000
--- a/pkg/detectors/abuseipdb/abuseipdb_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package abuseipdb
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAbuseipdb_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to abuseipdb API
- [DEBUG] Using API_KEY=o8oqti3tghu2xic76ii4t7jb9bxuzd4200j1yrkdjl6s8834hx4dgz1wwo90diqraakjd13sljcjkfnf
- [INFO] Response received: 200 OK
- `,
- want: []string{"o8oqti3tghu2xic76ii4t7jb9bxuzd4200j1yrkdjl6s8834hx4dgz1wwo90diqraakjd13sljcjkfnf"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {abuseipdb}
- {abuseipdb AQAAABAAA zgtj0q3v38u4pthc6nmy02n60bj244u5o9j47ln1jlue5mxzaasfi29x4dzcbxroawvkm26thtr61066}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"zgtj0q3v38u4pthc6nmy02n60bj244u5o9j47ln1jlue5mxzaasfi29x4dzcbxroawvkm26thtr61066"},
- },
- {
- name: "valid pattern - out of prefix range",
- input: `
- [INFO] Sending request to abuseipdb API
- [INFO] Processing request
- [Info] Response received: 200 OK
- [DEBUG] Used API_KEY=o8oqti3tghu2xic76ii4t7jb9bxuzd4200j1yrkdjl6s8834hx4dgz1wwo90diqraakjd13sljcjkfnf
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to abuseipdb API
- [DEBUG] Using API_KEY=7e4abcdef456Ghijkl789mnopqr012stuvwx3455123abcdef456ghijkl789mnopqr012stuvwX
- [ERROR] Response received: 400 BadRequest
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/abyssale/abyssale.go b/pkg/detectors/abyssale/abyssale.go
deleted file mode 100644
index 8e2e602ff40b..000000000000
--- a/pkg/detectors/abyssale/abyssale.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package abyssale
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-const abyssaleURL = "https://api.abyssale.com"
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
-
- 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{"abyssale"}) + `\b([a-z0-9A-Z]{40})\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{"abyssale"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Abyssale secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Abyssale,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyAbyssale(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAbyssale(ctx context.Context, client *http.Client, resMatch string) (bool, error) {
- // https://developers.abyssale.com/rest-api/authentication
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, abyssaleURL+"/ready", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("x-api-key", resMatch)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Abyssale
-}
-
-func (s Scanner) Description() string {
- return "Abyssale is a service offering various API functionalities for marketing automation and services such as images and ad campaigns. Abyssale API keys can be used to access and interact with this data."
-}
diff --git a/pkg/detectors/abyssale/abyssale_integration_test.go b/pkg/detectors/abyssale/abyssale_integration_test.go
deleted file mode 100644
index bcbf0d35e38a..000000000000
--- a/pkg/detectors/abyssale/abyssale_integration_test.go
+++ /dev/null
@@ -1,169 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package abyssale
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-)
-
-func TestAbyssale_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ABYSSALE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("ABYSSALE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abyssale secret %s within but verified", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Abyssale,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abyssale secret %s within but verified", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Abyssale,
- Verified: false,
- }
- r.SetVerificationError(context.DeadlineExceeded)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "{}")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abyssale secret %s within but verified", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Abyssale,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a abyssale secret %s within but verified", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Abyssale,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Abyssale.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("Abyssale.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/abyssale/abyssale_test.go b/pkg/detectors/abyssale/abyssale_test.go
deleted file mode 100644
index f57e86781e1a..000000000000
--- a/pkg/detectors/abyssale/abyssale_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package abyssale
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAbyssale_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to abyssale API
- [DEBUG] Using API_KEY=rWE8I0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G
- [INFO] Response received: 200 OK
- `,
- want: []string{"rWE8I0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {abyssale}
- {abyssale AQAAABAAA xTiPNSDg6JjzG8fWoLb8JlE8SBcMKkCx2fZLZD91}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"xTiPNSDg6JjzG8fWoLb8JlE8SBcMKkCx2fZLZD91"},
- },
- {
- name: "valid pattern - out of prefix range",
- input: `
- [INFO] Sending request to abyssale API
- [INFO] Processing request
- [Info] Response received: 200 OK
- [DEBUG] Used API_KEY=rWE8I0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to abyssale API
- [DEBUG] Using API_KEY=rWE8_0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G
- [ERROR] Response received: 400 BadRequest
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/account_filter.go b/pkg/detectors/account_filter.go
deleted file mode 100644
index 832bf1f76e4c..000000000000
--- a/pkg/detectors/account_filter.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package detectors
-
-// AccountFilter implements account-based filtering functionality that detectors can embed
-// to gain allow and deny list capabilities for account IDs.
-type AccountFilter struct {
- allowedAccounts map[string]struct{}
- deniedAccounts map[string]struct{}
-}
-
-// SetAllowedAccounts configures the allowed account IDs.
-// If set, only accounts in this list will be verified.
-func (a *AccountFilter) SetAllowedAccounts(accountIDs []string) {
- if len(accountIDs) == 0 {
- a.allowedAccounts = nil
- return
- }
-
- accounts := make(map[string]struct{}, len(accountIDs))
- for _, accountID := range accountIDs {
- accounts[accountID] = struct{}{}
- }
- a.allowedAccounts = accounts
-}
-
-// SetDeniedAccounts configures the denied account IDs.
-// Accounts in this list will never be verified.
-func (a *AccountFilter) SetDeniedAccounts(accountIDs []string) {
- if len(accountIDs) == 0 {
- a.deniedAccounts = nil
- return
- }
-
- accounts := make(map[string]struct{}, len(accountIDs))
- for _, accountID := range accountIDs {
- accounts[accountID] = struct{}{}
- }
- a.deniedAccounts = accounts
-}
-
-// ShouldSkipAccount checks if an account ID should be skipped for verification
-// based on allow and deny lists.
-//
-// Precedence: deny list > allow list (if account is in both, it's denied)
-func (a *AccountFilter) ShouldSkipAccount(accountID string) bool {
- // Check deny list first - takes precedence
- if len(a.deniedAccounts) > 0 {
- if _, isDenied := a.deniedAccounts[accountID]; isDenied {
- return true
- }
- }
-
- // Check allow list - if populated, account must be in it
- if len(a.allowedAccounts) > 0 {
- if _, isAllowed := a.allowedAccounts[accountID]; !isAllowed {
- return true
- }
- }
-
- // Account is allowed for verification
- return false
-}
-
-// IsInDenyList checks if an account ID is in the deny list
-func (a *AccountFilter) IsInDenyList(accountID string) bool {
- if len(a.deniedAccounts) == 0 {
- return false
- }
- _, isDenied := a.deniedAccounts[accountID]
- return isDenied
-}
-
-// IsInAllowList checks if an account ID is in the allow list
-func (a *AccountFilter) IsInAllowList(accountID string) bool {
- if len(a.allowedAccounts) == 0 {
- return false
- }
- _, isAllowed := a.allowedAccounts[accountID]
- return isAllowed
-}
diff --git a/pkg/detectors/account_filter_test.go b/pkg/detectors/account_filter_test.go
deleted file mode 100644
index a5a6a4c19571..000000000000
--- a/pkg/detectors/account_filter_test.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package detectors
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestEmbeddedAccountFilter(t *testing.T) {
- type Scanner struct{ AccountFilter }
-
- t.Run("no filtering configured - should not skip", func(t *testing.T) {
- var s Scanner // Fresh instance for this test
-
- shouldSkip := s.ShouldSkipAccount("test-account")
- assert.False(t, shouldSkip)
- assert.False(t, s.IsInDenyList("test-account"))
- assert.False(t, s.IsInAllowList("test-account"))
- })
-
- t.Run("allowed accounts only", func(t *testing.T) {
- var s Scanner // Fresh instance for this test
- s.SetAllowedAccounts([]string{"allowed-account-1", "allowed-account-2"})
-
- // Account in allow list - should not skip
- shouldSkip := s.ShouldSkipAccount("allowed-account-1")
- assert.False(t, shouldSkip)
- assert.True(t, s.IsInAllowList("allowed-account-1"))
-
- // Account not in allow list - should skip
- shouldSkip = s.ShouldSkipAccount("other-account")
- assert.True(t, shouldSkip)
- assert.False(t, s.IsInAllowList("other-account"))
- })
-
- t.Run("denied accounts only", func(t *testing.T) {
- var s Scanner // Fresh instance for this test
- s.SetDeniedAccounts([]string{"denied-account-1", "denied-account-2"})
-
- // Account in deny list - should skip
- shouldSkip := s.ShouldSkipAccount("denied-account-1")
- assert.True(t, shouldSkip)
- assert.True(t, s.IsInDenyList("denied-account-1"))
-
- // Account not in deny list - should not skip (no allow list restrictions)
- shouldSkip = s.ShouldSkipAccount("other-account")
- assert.False(t, shouldSkip)
- assert.False(t, s.IsInDenyList("other-account"))
- })
-
- t.Run("deny list takes precedence over allow list", func(t *testing.T) {
- var s Scanner // Fresh instance for this test
- s.SetAllowedAccounts([]string{"conflicted-account", "allowed-only-account"})
- s.SetDeniedAccounts([]string{"conflicted-account"}) // Same account in both lists
-
- // Account is in both allow and deny lists - deny takes precedence
- shouldSkip := s.ShouldSkipAccount("conflicted-account")
- assert.True(t, shouldSkip)
- assert.True(t, s.IsInDenyList("conflicted-account"))
- assert.True(t, s.IsInAllowList("conflicted-account"))
-
- // Account only in allow list - should not skip
- shouldSkip = s.ShouldSkipAccount("allowed-only-account")
- assert.False(t, shouldSkip)
- assert.False(t, s.IsInDenyList("allowed-only-account"))
- assert.True(t, s.IsInAllowList("allowed-only-account"))
- })
-
- t.Run("allow list with denied account not in allow list", func(t *testing.T) {
- var s Scanner // Fresh instance for this test
- s.SetAllowedAccounts([]string{"trusted-account"}) // Allow one account
- s.SetDeniedAccounts([]string{"blocked-account"}) // Deny different account
-
- // Account in deny list (not in allow list) - should skip due to deny list
- shouldSkip := s.ShouldSkipAccount("blocked-account")
- assert.True(t, shouldSkip)
- assert.True(t, s.IsInDenyList("blocked-account"))
- assert.False(t, s.IsInAllowList("blocked-account"))
-
- // Account in allow list (not in deny list) - should not skip
- shouldSkip = s.ShouldSkipAccount("trusted-account")
- assert.False(t, shouldSkip)
- assert.False(t, s.IsInDenyList("trusted-account"))
- assert.True(t, s.IsInAllowList("trusted-account"))
-
- // Account in neither list - should skip due to allow list restriction
- shouldSkip = s.ShouldSkipAccount("unknown-account")
- assert.True(t, shouldSkip)
- assert.False(t, s.IsInDenyList("unknown-account"))
- assert.False(t, s.IsInAllowList("unknown-account"))
- })
-
- t.Run("clearing lists", func(t *testing.T) {
- var s Scanner // Fresh instance for this test
- s.SetAllowedAccounts([]string{"initial-allowed"})
- s.SetDeniedAccounts([]string{"initial-denied"})
-
- // Verify initial state
- assert.True(t, s.ShouldSkipAccount("random-account")) // Not in allow list
- assert.True(t, s.ShouldSkipAccount("initial-denied")) // In deny list
-
- // Clear allowed accounts with nil
- s.SetAllowedAccounts(nil)
- assert.False(t, s.ShouldSkipAccount("random-account")) // No allow list restriction
- assert.True(t, s.ShouldSkipAccount("initial-denied")) // Still in deny list
-
- // Clear denied accounts with empty slice
- s.SetDeniedAccounts([]string{})
- assert.False(t, s.ShouldSkipAccount("initial-denied")) // No longer denied
- assert.False(t, s.ShouldSkipAccount("initial-allowed")) // No restrictions
- })
-}
diff --git a/pkg/detectors/accuweather/v1/accuweather.go b/pkg/detectors/accuweather/v1/accuweather.go
deleted file mode 100644
index 525a6d2a4ed2..000000000000
--- a/pkg/detectors/accuweather/v1/accuweather.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package accuweather
-
-import (
- "context"
- "fmt"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- Client *http.Client
-}
-
-const accuweatherURL = "https://dataservice.accuweather.com"
-const requiredShannonEntropy = 4
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
-
- 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{"accuweather"}) + `([a-z0-9A-Z\%]{35})\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{"accuweather"}
-}
-
-func (s Scanner) Version() int { return 1 }
-
-func (s Scanner) getClient() *http.Client {
- if s.Client != nil {
- return s.Client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Accuweather secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- matches := keyPat.FindAllStringSubmatch(string(data), -1)
- return s.ProcessMatches(ctx, matches, verify)
-}
-
-func (s Scanner) ProcessMatches(ctx context.Context, matches [][]string, verify bool) (results []detectors.Result, err error) {
- uniqueMatches := getUniqueMatches(matches)
- for key := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Accuweather,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyAccuweather(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, key)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func getUniqueMatches(allMatches [][]string) map[string]struct{} {
- uniqueMatches := map[string]struct{}{}
- for _, match := range allMatches {
- k := match[1]
- if detectors.StringShannonEntropy(k) < requiredShannonEntropy {
- continue
- }
- uniqueMatches[k] = struct{}{}
- }
- return uniqueMatches
-}
-
-func verifyAccuweather(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, accuweatherURL+"/locations/v1/cities/autocomplete?apikey="+key+"&q=----&language=en-us", nil)
- if err != nil {
- return false, err
- }
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- // https://developer.accuweather.com/accuweather-locations-api/apis/get/locations/v1/cities/autocomplete
- switch res.StatusCode {
- case http.StatusOK, http.StatusForbidden:
- // 403 indicates lack of permission, but valid token
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Accuweather
-}
-
-func (s Scanner) Description() string {
- return "AccuWeather is a weather forecasting service. AccuWeather API keys can be used to access weather data and forecasts."
-}
diff --git a/pkg/detectors/accuweather/v1/accuweather_integration_test.go b/pkg/detectors/accuweather/v1/accuweather_integration_test.go
deleted file mode 100644
index 68075082d1f7..000000000000
--- a/pkg/detectors/accuweather/v1/accuweather_integration_test.go
+++ /dev/null
@@ -1,168 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package accuweather
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAccuweather_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ACCUWEATHER")
- inactiveSecret := testSecrets.MustGetField("ACCUWEATHER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a accuweather secret %s within but verified", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Accuweather,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to timeout",
- s: Scanner{Client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a accuweather secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Accuweather,
- Verified: false,
- }
- r.SetVerificationError(context.DeadlineExceeded)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to unexpected api surface",
- s: Scanner{Client: common.ConstantResponseHttpClient(500, "{}")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a accuweather secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Accuweather,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a accuweather secret %s within but verified", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Accuweather,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Accuweather.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())
- }
- got[i].Raw = nil
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Accuweather.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/accuweather/v1/accuweather_test.go b/pkg/detectors/accuweather/v1/accuweather_test.go
deleted file mode 100644
index 0836c4dfb64c..000000000000
--- a/pkg/detectors/accuweather/v1/accuweather_test.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package accuweather
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAccuWeather_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to accuweather API
- [DEBUG] Using API_KEY=WAgP6m4gYc1qe%HnjWAAF5HBKL%i6kwrsbD
- [INFO] Response received: 200 OK
- `,
- want: []string{"WAgP6m4gYc1qe%HnjWAAF5HBKL%i6kwrsbD"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {accuweather}
- {accuweather AQAAABAAA ErOAU9rTSuX6IfHFGsJbpK3bCC1jIEX%gtj}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"ErOAU9rTSuX6IfHFGsJbpK3bCC1jIEX%gtj"},
- },
- {
- name: "valid pattern - out of prefix range",
- input: `
- [INFO] Sending request to accuweather API
- [INFO] Processing request
- [Info] Response received: 200 OK
- [DEBUG] Used API_KEY=WAgP6m4gYc1qe%HnjWAAF5HBKL%i6kwrsbD
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to accuweather API
- [DEBUG] Using API_KEY=WAgP6m4gYc1qe$HnjWAAF5HBKL%i6kwrsbD
- [Error] Response received: 400 BadRequest
- `,
- want: nil,
- },
- {
- name: "valid pattern - Shannon entropy below threshold",
- input: `
- [INFO] Sending request to accuweather API
- [DEBUG] Using API_KEY=WAAP6A4gYA1qA%HAaWAAFAHBAL%a6kwwwbD
- [ERROR] Response received: 400 BadRequest
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/accuweather/v2/accuweather.go b/pkg/detectors/accuweather/v2/accuweather.go
deleted file mode 100644
index f99875680b02..000000000000
--- a/pkg/detectors/accuweather/v2/accuweather.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package accuweather
-
-import (
- "context"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/accuweather/v1"
-)
-
-type Scanner struct {
- v1.Scanner
-}
-
-func (s Scanner) Version() int { return 2 }
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"accuweather"}) + `\b([a-zA-Z0-9]{32})\b`)
-)
-
-// FromData will find and optionally verify Accuweather secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- matches := keyPat.FindAllStringSubmatch(string(data), -1)
- return s.ProcessMatches(ctx, matches, verify)
-}
diff --git a/pkg/detectors/accuweather/v2/accuweather_integration_test.go b/pkg/detectors/accuweather/v2/accuweather_integration_test.go
deleted file mode 100644
index 2c377fd52b3c..000000000000
--- a/pkg/detectors/accuweather/v2/accuweather_integration_test.go
+++ /dev/null
@@ -1,169 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package accuweather
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/accuweather/v1"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAccuweather_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ACCUWEATHER")
- inactiveSecret := testSecrets.MustGetField("ACCUWEATHER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a accuweather secret %s within but verified", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Accuweather,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to timeout",
- s: Scanner{Scanner: v1.Scanner{Client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a accuweather secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Accuweather,
- Verified: false,
- }
- r.SetVerificationError(context.DeadlineExceeded)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to unexpected api surface",
- s: Scanner{Scanner: v1.Scanner{Client: common.ConstantResponseHttpClient(500, "{}")}},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a accuweather secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Accuweather,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a accuweather secret %s within but verified", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Accuweather,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Accuweather.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())
- }
- got[i].Raw = nil
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Accuweather.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/accuweather/v2/accuweather_test.go b/pkg/detectors/accuweather/v2/accuweather_test.go
deleted file mode 100644
index 85e65c7308e1..000000000000
--- a/pkg/detectors/accuweather/v2/accuweather_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package accuweather
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAccuWeather_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to accuweather API
- [DEBUG] Using API_KEY=Qh6DP6Zf7vHtmnDDsbS219qcz4d883Y9
- [INFO] Response received: 200 OK
- `,
- want: []string{"Qh6DP6Zf7vHtmnDDsbS219qcz4d883Y9"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {accuweather}
- {accuweather AQAAABAAA BJDD9bYh8bR586Wcw3F1lvkUYy3RZZbD}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"BJDD9bYh8bR586Wcw3F1lvkUYy3RZZbD"},
- },
- {
- name: "valid pattern - out of prefix range",
- input: `
- [INFO] Sending request to accuweather API
- [INFO] Processing request
- [Info] Response received: 200 OK
- [DEBUG] Used API_KEY=Qh6DP6Zf7vHtmnDDsbS219qcz4d883Y9
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to accuweather API
- [DEBUG] Using API_KEY=Qh6DP6Zf7vHtm@DDsbS219qcz4d883Y9
- [ERROR] Response received: 400 BadRequest
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/adafruitio/adafruitio.go b/pkg/detectors/adafruitio/adafruitio.go
deleted file mode 100644
index d36e82da4060..000000000000
--- a/pkg/detectors/adafruitio/adafruitio.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package adafruitio
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-const adafruitioURL = "https://io.adafruit.com"
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
-
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(aio\_[a-zA-Z0-9]{28})\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{"aio_"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify AdafruitIO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AdafruitIO,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyAdafruitIO(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAdafruitIO(ctx context.Context, client *http.Client, resMatch string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, adafruitioURL+"/api/v2/ladybugtest/feeds/?x-aio-key="+resMatch, nil)
- if err != nil {
- return false, err
- }
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- // https://learn.adafruit.com/adafruit-io/http-status-codes
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AdafruitIO
-}
-
-func (s Scanner) Description() string {
- return "Adafruit IO is a cloud service used for IoT applications. Adafruit IO keys can be used to access and control data and devices connected to the platform."
-}
diff --git a/pkg/detectors/adafruitio/adafruitio_integration_test.go b/pkg/detectors/adafruitio/adafruitio_integration_test.go
deleted file mode 100644
index 82702d0e9858..000000000000
--- a/pkg/detectors/adafruitio/adafruitio_integration_test.go
+++ /dev/null
@@ -1,167 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package adafruitio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAdafruitIO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ADAFRUITIO")
- inactiveSecret := testSecrets.MustGetField("ADAFRUITIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AdafruitIO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(10 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_AdafruitIO,
- Verified: false,
- }
- r.SetVerificationError(context.DeadlineExceeded)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "{}")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_AdafruitIO,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a adafruitio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AdafruitIO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AdafruitIO.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("AdafruitIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/adafruitio/adafruitio_test.go b/pkg/detectors/adafruitio/adafruitio_test.go
deleted file mode 100644
index 043bb8ef7336..000000000000
--- a/pkg/detectors/adafruitio/adafruitio_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package adafruitio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAdafruitio_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using API_KEY=aio_VxEqGaqgMgZej3DceezbBy03eWyW
- [INFO] Response received: 200 OK
- `,
- want: []string{"aio_VxEqGaqgMgZej3DceezbBy03eWyW"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {adafruitio}
- {AQAAABAAA aio_cQD77DF9SgsYbgWcxJbpLOlR5emX}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"aio_cQD77DF9SgsYbgWcxJbpLOlR5emX"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using API_KEY=aio_VxEqGaqgMgZej3DceezbBy03eWyWa
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/adobeio/adobeio.go b/pkg/detectors/adobeio/adobeio.go
deleted file mode 100644
index 81eedc3ea4b1..000000000000
--- a/pkg/detectors/adobeio/adobeio.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package adobeio
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"adobe"}) + `\b([a-z0-9]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"adobe"}) + `\b([a-zA-Z0-9.]{12})\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{"adobe"}
-}
-
-// FromData will find and optionally verify AdobeIO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys, uniqueIds = make(map[string]struct{}), make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueIds[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- for id := range uniqueIds {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AdobeIO,
- Raw: []byte(key),
- RawV2: []byte(key + id),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAdobeIOSecret(ctx, client, key, id)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func verifyAdobeIOSecret(ctx context.Context, client *http.Client, key string, id string) (bool, error) {
- url := "https://stock.adobe.io/Rest/Media/1/Search/Files?locale=en_US%2526search_parameters%255Bwords%255D=kittens"
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("x-api-key", key)
- req.Header.Add("x-product", id)
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AdobeIO
-}
-
-func (s Scanner) Description() string {
- return "AdobeIO provides APIs for integrating with Adobe services. These credentials can be used to access Adobe services and data."
-}
diff --git a/pkg/detectors/adobeio/adobeio_integration_test.go b/pkg/detectors/adobeio/adobeio_integration_test.go
deleted file mode 100644
index e1e3914b16d7..000000000000
--- a/pkg/detectors/adobeio/adobeio_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package adobeio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAdobeIO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ADOBEIO_TOKEN")
- id := testSecrets.MustGetField("ADOBEIO_PRODUCT")
- inactiveSecret := testSecrets.MustGetField("ADOBEIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a adobeio secret %s within adobeio %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AdobeIO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a adobeio secret %s within adobeio %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AdobeIO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AdobeIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AdobeIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/adobeio/adobeio_test.go b/pkg/detectors/adobeio/adobeio_test.go
deleted file mode 100644
index 1c854f78ea35..000000000000
--- a/pkg/detectors/adobeio/adobeio_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package adobeio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAdobeIO_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the adobe API
- [DEBUG] Using adobe KEY=zoaw0c0m50m0hz2h1fm21y4tqfyl7ifi
- [DEBUG] Using adobe ID=qCRbiIy1NJaW
- [INFO] Response received: 200 OK
- `,
- want: []string{"zoaw0c0m50m0hz2h1fm21y4tqfyl7ifiqCRbiIy1NJaW"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {adobe ftd7hkeafk0q}
- {adobe AQAAABAAA siybmtkgho9nsgjhng5yhp92wnir2a9t}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"siybmtkgho9nsgjhng5yhp92wnir2a9tftd7hkeafk0q"},
- },
- {
- name: "valid pattern - out of prefix range",
- input: `
- [INFO] Sending request to the adobe API
- [DEBUG] Using KEY=zoaw0c0m50m0hz2h1fm21y4tqfyl7ifi
- [DEBUG] Using ID=qCRbiIy1NJaW
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the adobe API
- [DEBUG] Using adobe KEY=Rzxc#0987$%bv1234poiu6749gtnrfv54
- [DEBUG] Using adobe ID=qCRbiIy1NJaW
- [ERROR] Response received: 400 BadRequest
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/adzuna/adzuna.go b/pkg/detectors/adzuna/adzuna.go
deleted file mode 100644
index c9b9416b828b..000000000000
--- a/pkg/detectors/adzuna/adzuna.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package adzuna
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-const adzunaURL = "https://api.adzuna.com"
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
-
- 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{"adzuna"}) + `\b([a-z0-9]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"adzuna"}) + `\b([a-z0-9]{8})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"adzuna"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Adzuna secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
- resIdMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Adzuna,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyAdzuna(ctx, client, resMatch, resIdMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyAdzuna(ctx context.Context, client *http.Client, resMatch, resIdMatch string) (bool, error) {
- // https://developer.adzuna.com/activedocs#!/adzuna/search
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, adzunaURL+fmt.Sprintf("/v1/api/jobs/us/search/1?app_id=%s&app_key=%s", resIdMatch, resMatch), nil)
- if err != nil {
- return false, err
- }
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- // https://developer.adzuna.com/overview
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Adzuna
-}
-
-func (s Scanner) Description() string {
- return "Adzuna is a job search engine used to find job listings. Adzuna API keys can be used to access job listing data."
-}
diff --git a/pkg/detectors/adzuna/adzuna_integration_test.go b/pkg/detectors/adzuna/adzuna_integration_test.go
deleted file mode 100644
index f2a1bf630dd0..000000000000
--- a/pkg/detectors/adzuna/adzuna_integration_test.go
+++ /dev/null
@@ -1,168 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package adzuna
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAdzuna_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ADZUNA")
- id := testSecrets.MustGetField("ADZUNA_ID")
- inactiveSecret := testSecrets.MustGetField("ADZUNA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a adzuna secret %s within adzuna %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Adzuna,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a adzuna secret %s within adzuna %s", secret, id)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Adzuna,
- Verified: false,
- }
- r.SetVerificationError(context.DeadlineExceeded)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "{}")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a adzuna secret %s within adzuna %s", secret, id)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Adzuna,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a adzuna secret %s within adzuna %s but not valid", inactiveSecret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Adzuna,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Adzuna.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("Adzuna.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/adzuna/adzuna_test.go b/pkg/detectors/adzuna/adzuna_test.go
deleted file mode 100644
index cd8a04bd11ad..000000000000
--- a/pkg/detectors/adzuna/adzuna_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package adzuna
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAdzuna_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the adzuna API
- [DEBUG] Using adzuna KEY=smcud4y6elxx7u6q58ewwv8rq01hpi3f
- [DEBUG] Using adzuna ID=cxu9w2g6
- [INFO] Response received: 200 OK
- `,
- want: []string{"smcud4y6elxx7u6q58ewwv8rq01hpi3fcxu9w2g6"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {adzuna svkit0wx}
- {adzuna AQAAABAAA atubvgvpd6jjo0ac1wjianofnpgr24ac}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"atubvgvpd6jjo0ac1wjianofnpgr24acsvkit0wx"},
- },
- {
- name: "valid pattern - out of prefix range",
- input: `
- [INFO] Sending request to the adzuna API
- [DEBUG] Using KEY=smcud4y6elxx7u6q58ewwv8rq01hpi3f
- [DEBUG] Using ID=cxu9w2g6
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "valid pattern - only key",
- input: `
- [INFO] Sending request to the adzuna API
- [DEBUG] Using KEY=smcud4y6elxx7u6q58ewwv8rq01hpi3f
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "valid pattern - only id",
- input: `
- [INFO] Sending request to the adzuna API
- [DEBUG] Using ID=cxu9w2g6
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the adzuna API
- [DEBUG] Using KEY=sxojb6ygb2wsx0o
- [DEBUG] Using ID=cxu9w2g6
- [ERROR] Response received: 400 BadRequest
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/aeroworkflow/aeroworkflow.go b/pkg/detectors/aeroworkflow/aeroworkflow.go
deleted file mode 100644
index 11b14b5c8ead..000000000000
--- a/pkg/detectors/aeroworkflow/aeroworkflow.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package aeroworkflow
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
-}
-
-const aeroworkflowURL = "https://api.aeroworkflow.com"
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
-
- 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{"aeroworkflow"}) + `\b([a-zA-Z0-9^!?#:*;]{20})`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"aeroworkflow"}) + `\b([0-9]{1,})\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{"aeroworkflow"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Aeroworkflow secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Aeroworkflow,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyAeroworkflow(ctx, client, resMatch, resIdMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func verifyAeroworkflow(ctx context.Context, client *http.Client, resMatch, resIdMatch string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, aeroworkflowURL+"/api/"+resIdMatch+"/me", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("apikey", resMatch)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- // https://api.aeroworkflow.com/swagger/index.html
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- // 401 for invalid API key
- // 403 for invalid Account ID
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Aeroworkflow
-}
-
-func (s Scanner) Description() string {
- return "Aeroworkflow is a service for managing workflows. Aeroworkflow API keys and Account IDs can be used to access and manage workflows."
-}
diff --git a/pkg/detectors/aeroworkflow/aeroworkflow_integration_test.go b/pkg/detectors/aeroworkflow/aeroworkflow_integration_test.go
deleted file mode 100644
index 3ea3ef555a84..000000000000
--- a/pkg/detectors/aeroworkflow/aeroworkflow_integration_test.go
+++ /dev/null
@@ -1,165 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package aeroworkflow
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAeroworkflow_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AEROWORKFLOW_SECRET")
- id := testSecrets.MustGetField("AEROWORKFLOW_ID")
- inactiveSecret := testSecrets.MustGetField("AEROWORKFLOW_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aeroworkflow secret %s within aeroworkflow %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Aeroworkflow,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aeroworkflow secret %s within aeroworkflow %s", secret, id)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Aeroworkflow,
- Verified: false,
- }
- r.SetVerificationError(context.DeadlineExceeded)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "{}")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aeroworkflow secret %s within aeroworkflow %s", secret, id)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Aeroworkflow,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aeroworkflow secret %s within aeroworkflow %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Aeroworkflow,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Aeroworkflow.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("Aeroworkflow.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/aeroworkflow/aeroworkflow_test.go b/pkg/detectors/aeroworkflow/aeroworkflow_test.go
deleted file mode 100644
index b554172b9aee..000000000000
--- a/pkg/detectors/aeroworkflow/aeroworkflow_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package aeroworkflow
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAeroWorkflow_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the aeroworkflow API
- [DEBUG] Using aeroworkflow KEY=VmFYK7WG3CkgVmTl:c*X
- [DEBUG] Using aeroworkflow ID=678436
- [INFO] Response received: 200 OK
- `,
- want: []string{"VmFYK7WG3CkgVmTl:c*X678436"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {aeroworkflow 6}
- {aeroworkflow AQAAABAAA XjPSUOhREIN:4HX2#akH}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"XjPSUOhREIN:4HX2#akH6"},
- },
- {
- name: "valid pattern - out of prefix range",
- input: `
- [INFO] Sending request to the aeroworkflow API
- [DEBUG] Using KEY=VmFYK7WG3CkgVmTl:c*X
- [DEBUG] Using ID=678436
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "valid pattern - only key",
- input: `
- [INFO] Sending request to the aeroworkflow API
- [DEBUG] Using KEY=VmFYK7WG3CkgVmTl:c*X
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "valid pattern - only id",
- input: `
- [INFO] Sending request to the aeroworkflow API
- [DEBUG] Using ID=678436
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the aeroworkflow API
- [DEBUG] Using KEY=VmFYK7WG3CkgVmTl:c*X
- [DEBUG] Using ID=cxu9w2g6
- [ERROR] Response received: 400 BadRequest
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/agora/agora.go b/pkg/detectors/agora/agora.go
deleted file mode 100644
index 5ffad3f394ec..000000000000
--- a/pkg/detectors/agora/agora.go
+++ /dev/null
@@ -1,117 +0,0 @@
-package agora
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
-}
-
-const agoraURL = "https://api.agora.io"
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
-
- 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{"agora", "key", "token"}) + `\b([a-z0-9]{32})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"agora", "secret"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"agora"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Agora secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, secret := range secretMatches {
- resSecret := strings.TrimSpace(secret[1])
- /*
- as both agora key and secretMatch has same regex, the set of strings keyMatch for both probably me same.
- we need to avoid the scenario where key is same as secretMatch. This will reduce the number of matches we process.
- */
- if resMatch == resSecret {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Agora,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resSecret),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyAgora(ctx, client, resMatch, resSecret)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyAgora(ctx context.Context, client *http.Client, resMatch, resSecret string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, agoraURL+"/dev/v1/projects", nil)
- if err != nil {
- return false, err
- }
- req.SetBasicAuth(resSecret, resMatch)
- res, err := client.Do(req)
-
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- // https://docs.agora.io/en/voice-calling/reference/agora-console-rest-api#get-all-projects
- switch res.StatusCode {
- case http.StatusOK, http.StatusCreated:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Agora
-}
-
-func (s Scanner) Description() string {
- return "Agora is a real-time engagement platform providing APIs for voice, video, and messaging. Agora API keys can be used to access and manage these services."
-}
diff --git a/pkg/detectors/agora/agora_integration_test.go b/pkg/detectors/agora/agora_integration_test.go
deleted file mode 100644
index 2c8b20f28246..000000000000
--- a/pkg/detectors/agora/agora_integration_test.go
+++ /dev/null
@@ -1,176 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package agora
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAgora_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- id := testSecrets.MustGetField("AGORA")
- secret := testSecrets.MustGetField("AGORA_SECRET")
- inactiveSecret := testSecrets.MustGetField("AGORA_SECRET_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a agora secret %s within agora id %s but verified", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Agora,
- Verified: true,
- },
- {
- DetectorType: detectorspb.DetectorType_Agora,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a agora secret %s within agora id %s but verified", secret, id)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Agora,
- Verified: false,
- }
- r.SetVerificationError(context.DeadlineExceeded)
- return []detectors.Result{r, r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "{}")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a agora secret %s within agora id %s but verified", secret, id)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Agora,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
- return []detectors.Result{r, r}
- }(),
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a agora secret %s within agora id %s but not valid ", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Agora,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Agora,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Agora.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- 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("Agora.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/agora/agora_test.go b/pkg/detectors/agora/agora_test.go
deleted file mode 100644
index 13e6aac8db51..000000000000
--- a/pkg/detectors/agora/agora_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package agora
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAgora_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the agora API
- [DEBUG] Using Token=6p77f9gjhxx9mwdj86of7y7820bh49vw
- [DEBUG] Using Secret=qi6txx6vd0qzn6j01xj9rr6clyejvjw5
- [INFO] Response received: 200 OK
- `,
- want: []string{"6p77f9gjhxx9mwdj86of7y7820bh49vwqi6txx6vd0qzn6j01xj9rr6clyejvjw5"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {agora 3devtbiys8b282kidr9u78kjq8xdtlo1}
- {AQAAABAAA bc7c6tag5jfuhz4y7v6v05dx2wq2z1ua}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"3devtbiys8b282kidr9u78kjq8xdtlo1bc7c6tag5jfuhz4y7v6v05dx2wq2z1ua"},
- },
- {
- name: "valid pattern - out of prefix range",
- input: `
- [INFO] Sending request to the agora API
- [DEBUG] Using 6p77f9gjhxx9mwdj86of7y7820bh49vw
- [DEBUG] Using qi6txx6vd0qzn6j01xj9rr6clyejvjw5
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "valid pattern - only key",
- input: `
- [INFO] Sending request to the agora API
- [DEBUG] Using Key=6p77f9gjhxx9mwdj86of7y7820bh49vw
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "valid pattern - only secret",
- input: `
- [INFO] Sending request to the agora API
- [DEBUG] Using Secret=qi6txx6vd0qzn6j01xj9rr6clyejvjw5
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the agora API
- [DEBUG] Using KEY=qi6txx6vd0qzn6j01xj9rr6clyejvjw
- [DEBUG] Using ID=qi6txx6vd0qzn6j01xj9rr6clyejvjw5yt
- [ERROR] Response received: 400 BadRequest
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/aha/aha.go b/pkg/detectors/aha/aha.go
deleted file mode 100644
index 1a50553dc2c8..000000000000
--- a/pkg/detectors/aha/aha.go
+++ /dev/null
@@ -1,117 +0,0 @@
-package aha
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
-}
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
-
- defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"aha"}) + `\b([0-9a-f]{64})\b`)
- URLPat = regexp.MustCompile(`\b([A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])\.aha\.io)`)
-)
-
-// 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{"aha.io"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Aha
-}
-
-func (s Scanner) Description() string {
- return "Aha is a product management software suite. Aha API keys can be used to access and modify product data and workflows."
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Aha secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueFoundUrls = make(map[string]struct{})
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range URLPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueFoundUrls[match[1]] = struct{}{}
- }
-
- // if no url was found use the default
- if len(uniqueFoundUrls) == 0 {
- uniqueFoundUrls["aha.io"] = struct{}{}
- }
-
- for _, match := range matches {
- for url := range uniqueFoundUrls {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Aha,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + url),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyAha(ctx, client, resMatch, url)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyAha(ctx context.Context, client *http.Client, resMatch, resURLMatch string) (bool, error) {
- url := fmt.Sprintf("https://%s/api/v1/me", resURLMatch)
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Accept", "application/vnd.aha+json; version=3")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- // https://www.aha.io/api
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusNotFound, http.StatusForbidden:
- // 403 is a known case where an account is inactive bc of a trial ending or payment issue
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
diff --git a/pkg/detectors/aha/aha_integration_test.go b/pkg/detectors/aha/aha_integration_test.go
deleted file mode 100644
index 548beb044bfa..000000000000
--- a/pkg/detectors/aha/aha_integration_test.go
+++ /dev/null
@@ -1,168 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package aha
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAha_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- domain := testSecrets.MustGetField("AHA_DOMAIN")
- secret := testSecrets.MustGetField("AHA_SECRET")
- inactiveSecret := testSecrets.MustGetField("AHA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{client: common.ConstantResponseHttpClient(200, "{}")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aha secret %s within %s but verified", secret, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Aha,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aha secret %s within %s but verified", secret, domain)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Aha,
- Verified: false,
- }
- r.SetVerificationError(context.DeadlineExceeded)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "{}")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aha secret %s within %s but verified", secret, domain)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Aha,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aha secret %s within but not valid domain %s", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Aha,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Aha.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("Aha.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/aha/aha_test.go b/pkg/detectors/aha/aha_test.go
deleted file mode 100644
index baf5d228497e..000000000000
--- a/pkg/detectors/aha/aha_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package aha
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAha_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] sending request to the aha.io API
- [DEBUG] using key = 81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541c
- [DEBUG] using host = example.aha.io
- [INFO] response received: 200 OK
- `,
- want: []string{"81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541cexample.aha.io"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {aha 3af0b286b668d9636fd68076d6c87a333fe285fd41593cfceab36b35606c915a}
- {AQAAABAAA ACTp3nufSEO791nIReS5udnRVFcG9j6-CqBJogBxo1pbql.aha.io}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"3af0b286b668d9636fd68076d6c87a333fe285fd41593cfceab36b35606c915aACTp3nufSEO791nIReS5udnRVFcG9j6-CqBJogBxo1pbql.aha.io"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [INFO] sending request to the aha.io API
- [WARN] Do not commit the secrets
- [DEBUG] using key = 81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541c
- [DEBUG] using host = example.aha.io
- [INFO] response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "valid pattern - only key",
- input: `
- [INFO] sending request to the aha.io API
- [DEBUG] using key = 81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541c
- [INFO] response received: 200 OK
- `,
- want: []string{"81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541caha.io"},
- },
- {
- name: "valid pattern - only URL",
- input: `
- [INFO] sending request to the example.aha.io API
- [INFO] response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] sending request to the aha.io API
- [DEBUG] using key = 81a1411a7e276fd88819df3137eJ406e0f281f8a8c417947ca4b025890c8541c
- [DEBUG] using host = 1test.aha.io
- [INFO] response received: 200 OK
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/airbrakeprojectkey/airbrakeprojectkey.go b/pkg/detectors/airbrakeprojectkey/airbrakeprojectkey.go
deleted file mode 100644
index 29ec88769102..000000000000
--- a/pkg/detectors/airbrakeprojectkey/airbrakeprojectkey.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package airbrakeprojectkey
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"airbrake"}) + `\b([a-zA-Z-0-9]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"airbrake"}) + `\b([0-9]{6})\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{"airbrake"}
-}
-
-// FromData will find and optionally verify AirbrakeProjectKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys, uniqueIds = make(map[string]struct{}), make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueIds[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- for id := range uniqueIds {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AirbrakeProjectKey,
- Raw: []byte(key),
- RawV2: []byte(key + id),
- }
- s1.ExtraData = map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/airbrake/",
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAirbrakeProjectKey(ctx, client, key, id)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
-
- }
-
- }
-
- return results, nil
-}
-
-func verifyAirbrakeProjectKey(ctx context.Context, client *http.Client, key string, id string) (bool, error) {
- url := "https://api.airbrake.io/api/v4/projects/" + id + "/deploys?key=" + key
- payload := strings.NewReader(`{"environment":"production","username":"john","email":"john@smith.com","repository":"https://github.com/airbrake/airbrake","revision":"38748467ea579e7ae64f7815452307c9d05e05c5","version":"v2.0"}`)
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, payload)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- // handle according to detector API responses.
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AirbrakeProjectKey
-}
-
-func (s Scanner) Description() string {
- return "Airbrake is an error and performance monitoring service for web applications. Airbrake project keys can be used to report and track errors in applications."
-}
diff --git a/pkg/detectors/airbrakeprojectkey/airbrakeprojectkey_integration_test.go b/pkg/detectors/airbrakeprojectkey/airbrakeprojectkey_integration_test.go
deleted file mode 100644
index 1dc9cc5ec686..000000000000
--- a/pkg/detectors/airbrakeprojectkey/airbrakeprojectkey_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package airbrakeprojectkey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAirbrakeProjectKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AIRBRAKEPROJECTKEY_TOKEN")
- id := testSecrets.MustGetField("AIRBRAKEPROJECTKEY_ID")
- inactiveSecret := testSecrets.MustGetField("AIRBRAKEPROJECTKEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airbrake secret %s within airbrake %s but verified", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirbrakeProjectKey,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airbrake secret %s within airbrake %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirbrakeProjectKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AirbrakeProjectKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AirbrakeProjectKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/airbrakeprojectkey/airbrakeprojectkey_test.go b/pkg/detectors/airbrakeprojectkey/airbrakeprojectkey_test.go
deleted file mode 100644
index 3122824a8c3b..000000000000
--- a/pkg/detectors/airbrakeprojectkey/airbrakeprojectkey_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package airbrakeprojectkey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAirBrakeProjectKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the airbrake API
- [DEBUG] Using airbrake Key=7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR
- [DEBUG] Using airbrake ID=856019
- [INFO] Response received: 200 OK
- `,
- want: []string{"7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR856019"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {airbrake 691149}
- {airbrake AQAAABAAA hYNK8PlcGXZ6PXXDFJI89LCjpoM8koTx}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"hYNK8PlcGXZ6PXXDFJI89LCjpoM8koTx691149"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [INFO] airbrake API request handling
- [INFO] Sending request to the API
- [DEBUG] Using Key=7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR
- [DEBUG] Using ID=856019
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "valid pattern - only key",
- input: `
- [INFO] Sending request to the airbrake API
- [DEBUG] Using airbrake Key=7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "valid pattern - only ID",
- input: `
- [INFO] Sending request to the airbrake API
- [DEBUG] Using airbrake ID=856019
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the airbrake API
- [DEBUG] Using airbrake Key=qwmnerBv56zx**cvkjqr78afvYU$90Op
- [DEBUG] Using airbrake ID=856019
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/airbrakeuserkey/airbrakeuserkey.go b/pkg/detectors/airbrakeuserkey/airbrakeuserkey.go
deleted file mode 100644
index 97515e406fe0..000000000000
--- a/pkg/detectors/airbrakeuserkey/airbrakeuserkey.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package airbrakeuserkey
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"airbrake"}) + `\b([a-zA-Z-0-9]{40})\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{"airbrake"}
-}
-
-// FromData will find and optionally verify AirbrakeUserKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AirbrakeUserKey,
- Raw: []byte(key),
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/airbrake/",
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAirbrakeUserKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAirbrakeUserKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.airbrake.io/api/v4/projects?key="+key, nil)
- if err != nil {
- return false, err
- }
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AirbrakeUserKey
-}
-
-func (s Scanner) Description() string {
- return "Airbrake is an error and performance monitoring service. Airbrake User Keys can be used to access and manage error reports and performance data."
-}
diff --git a/pkg/detectors/airbrakeuserkey/airbrakeuserkey_integration_test.go b/pkg/detectors/airbrakeuserkey/airbrakeuserkey_integration_test.go
deleted file mode 100644
index 7e0f5fabc324..000000000000
--- a/pkg/detectors/airbrakeuserkey/airbrakeuserkey_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package airbrakeuserkey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAirbrakeUserKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AIRBRAKEUSERKEY_TOKEN")
- inactiveSecret := testSecrets.MustGetField("AIRBRAKEUSERKEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airbrakeuserkey secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirbrakeUserKey,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airbrakeuserkey secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirbrakeUserKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AirbrakeUserKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AirbrakeUserKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/airbrakeuserkey/airbrakeuserkey_test.go b/pkg/detectors/airbrakeuserkey/airbrakeuserkey_test.go
deleted file mode 100644
index 6c38339876cf..000000000000
--- a/pkg/detectors/airbrakeuserkey/airbrakeuserkey_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package airbrakeuserkey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAirBrakeUserKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the airbrake API
- [DEBUG] Using Key=qsCGuilpkk2ngrsz75wtYqsCGuilpkk2ngrsz75w
- [INFO] Response received: 200 OK
- `,
- want: []string{"qsCGuilpkk2ngrsz75wtYqsCGuilpkk2ngrsz75w"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {airbrake 691149}
- {airbrake AQAAABAAA UTDwMhGhuk0T04V0yqTqcKIwSSp7syUyQRG8JwoF}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"UTDwMhGhuk0T04V0yqTqcKIwSSp7syUyQRG8JwoF"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [DEBUG] airbrake api processing
- [INFO] Sending request to the API
- [DEBUG] Using Key=qsCGuilpkk2ngrsz75wtYqsCGuilpkk2ngrsz75w
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the airbrake API
- [DEBUG] Using airbrake Key=Qs%CGuil#pkk2ngrsz75wtYqsCGuilpkk2ngrsz75w
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/airship/airship.go b/pkg/detectors/airship/airship.go
deleted file mode 100644
index d2d83565f5b7..000000000000
--- a/pkg/detectors/airship/airship.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package airship
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"airship"}) + `\b([0-9a-zA-Z]{91})\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{"airship"}
-}
-
-// FromData will find and optionally verify Airship secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Airship,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAirshipKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAirshipKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://go.urbanairship.com/api/schedules", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Accept", "application/vnd.urbanairship+json; version=3")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Airship
-}
-
-func (s Scanner) Description() string {
- return "Airship is a customer engagement platform that provides tools for mobile app messaging, in-app messaging, and web notifications. Airship API keys can be used to access and manage these messaging services."
-}
diff --git a/pkg/detectors/airship/airship_integration_test.go b/pkg/detectors/airship/airship_integration_test.go
deleted file mode 100644
index 8969b280b365..000000000000
--- a/pkg/detectors/airship/airship_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package airship
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAirship_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AIRSHIP")
- inactiveSecret := testSecrets.MustGetField("AIRSHIP_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airship secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Airship,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airship secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Airship,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Airship.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Airship.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/airship/airship_test.go b/pkg/detectors/airship/airship_test.go
deleted file mode 100644
index a3923ce9c95e..000000000000
--- a/pkg/detectors/airship/airship_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package airship
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAirship_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the airship API
- [DEBUG] Using Key=O3BV99CUDw3xYUAL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W
- [INFO] Response received: 200 OK
- `,
- want: []string{"O3BV99CUDw3xYUAL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {airship}
- {airship AQAAABAAA oVH3yIO1oAoXpK9Rc01EGNNTuw6d4Zyt07YNFmje644Ht00hvAaYwldNOV9vIPQw6dYHJLRgp2f75YdJ9OiICkYVhMI}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"oVH3yIO1oAoXpK9Rc01EGNNTuw6d4Zyt07YNFmje644Ht00hvAaYwldNOV9vIPQw6dYHJLRgp2f75YdJ9OiICkYVhMI"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [DEBUG] airship api processing
- [INFO] Sending request to the API
- [DEBUG] Using Key=O3BV99CUDw3xYUAL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the airship API
- [DEBUG] Using Key=O3BV99CUDw3xY#AL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/airtableoauth/airtableoauth.go b/pkg/detectors/airtableoauth/airtableoauth.go
deleted file mode 100644
index cdb9b8a750cf..000000000000
--- a/pkg/detectors/airtableoauth/airtableoauth.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package airtableoauth
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // The detector will attempt to match access tokens generated through the Airtable OAuth flow
- // Airtable OAuth does not support generating access tokens using client ID and key
- // Reference: https://airtable.com/developers/web/api/oauth-reference
- tokenPat = regexp.MustCompile(`\b([[:alnum:]]+\.v1\.[a-zA-Z0-9_-]+\.[a-f0-9]+)\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{"airtable"}
-}
-
-// FromData will find and optionally verify AirtableOAuth secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range tokenPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AirtableOAuth,
- Raw: []byte(match),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, match)
-
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{"token": match}
- }
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
- endpoint := "https://api.airtable.com/v0/meta/whoami"
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AirtableOAuth
-}
-
-func (s Scanner) Description() string {
- return "Airtable is a cloud collaboration service that offers database-like features. Airtable OAuth tokens can be used to access and modify data within Airtable bases."
-}
diff --git a/pkg/detectors/airtableoauth/airtableoauth_integration_test.go b/pkg/detectors/airtableoauth/airtableoauth_integration_test.go
deleted file mode 100644
index 1477185143f1..000000000000
--- a/pkg/detectors/airtableoauth/airtableoauth_integration_test.go
+++ /dev/null
@@ -1,167 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package airtableoauth
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-// TestAirtableoauth_FromChunk verifies the validity of an Airtable OAuth token
-// Note: The token validity test relies on an access token stored in the GCP secret manager.
-// Since Airtable OAuth tokens expire after 60 minutes, this test will eventually fail once the token becomes invalid.
-// The official guide linked below can be followed in order to generate a new valid access token:
-// https://airtable.com/developers/web/api/oauth-reference
-
-func TestAirtableoauth_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AIRTABLEOAUTH")
- inactiveSecret := testSecrets.MustGetField("AIRTABLEOAUTH_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airtableoauth secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirtableOAuth,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airtableoauth secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirtableOAuth,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airtableoauth secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirtableOAuth,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airtableoauth secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirtableOAuth,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Airtableoauth.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Airtableoauth.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/airtableoauth/airtableoauth_test.go b/pkg/detectors/airtableoauth/airtableoauth_test.go
deleted file mode 100644
index dbef0684bc4d..000000000000
--- a/pkg/detectors/airtableoauth/airtableoauth_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package airtableoauth
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAirtableoauth_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the airtable API
- [DEBUG] Using Key=oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0
- [INFO] Response received: 200 OK
- `,
- want: []string{"oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {airtable}
- {airtable AQAAABAAA iKMJv6D1mmUvunFTZLfm4RrYhdrt5JCBMv.v1.r8IBnGw7b_vW0fl0MDJqPRUEsDdHtNYW9ANwPFm40V_M4knoEaulKL-5lmtWoRq9fjG-GORe8efob5e658nTiOkdYC.8a8d3}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"iKMJv6D1mmUvunFTZLfm4RrYhdrt5JCBMv.v1.r8IBnGw7b_vW0fl0MDJqPRUEsDdHtNYW9ANwPFm40V_M4knoEaulKL-5lmtWoRq9fjG-GORe8efob5e658nTiOkdYC.8a8d3"},
- },
- {
- name: "finds all matches",
- input: `
- [INFO] Sending request to the airtable API
- [DEBUG] Using Key=oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0
- [ERROR] Response received: 401 UnAuthorized
- [DEBUG] Using Key=oaaRYiYSlTFXZzxDM.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwiZXhwaXJlc0F0IjoiMjAyNS0wMS0yOVQwMDowMTo0NC4wMDBaIiwic2VjcmV0IjoiZjYyOWE1MWVkM2M0ZjU5ODlmOTcyMDU1ZjkwODk3NDA4NmU0NjQxY2JhODU5Y2FhZTJkZjliMWQwODg0ZjIzMiJ9.27a8998029ac9bdd599b435572821dcb63c60cbf62b9cb2ba2a73511e5553d66
- [INFO] Response received: 200 OK
- `,
- want: []string{
- "oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0",
- "oaaRYiYSlTFXZzxDM.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwiZXhwaXJlc0F0IjoiMjAyNS0wMS0yOVQwMDowMTo0NC4wMDBaIiwic2VjcmV0IjoiZjYyOWE1MWVkM2M0ZjU5ODlmOTcyMDU1ZjkwODk3NDA4NmU0NjQxY2JhODU5Y2FhZTJkZjliMWQwODg0ZjIzMiJ9.27a8998029ac9bdd599b435572821dcb63c60cbf62b9cb2ba2a73511e5553d66",
- },
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the airtable API
- [DEBUG] Using Key=oaaRYiYSlTFXZzxDM.v2.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwiZXhwaXJlc0F0IjoiMjAyNS0wMS0yOVQwMDowMTo0NC4wMDBaIiwic2VjcmV0IjoiZjYyOWE1MWVkM2M0ZjU5ODlmOTcyMDU1ZjkwODk3NDA4NmU0NjQxY2JhODU5Y2FhZTJkZjliMWQwODg0ZjIzMiJ9.27a8998029ac9bdd599b435572821dcb63c60cbf62b9cb2ba2a73511e5553d66
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken.go b/pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken.go
deleted file mode 100644
index f3ef580b41ac..000000000000
--- a/pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package airtablepersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- tokenPat = regexp.MustCompile(detectors.PrefixRegex([]string{"airtable"}) + `\b(pat[[:alnum:]]{14}\.[a-f0-9]{64})\b`)
-)
-
-func (s Scanner) Keywords() []string {
- return []string{"airtable"}
-}
-
-// FromData will find and optionally verify AirtableOAuth secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range tokenPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AirtablePersonalAccessToken,
- Raw: []byte(match),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, match)
-
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{"token": match}
- }
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
- endpoint := "https://api.airtable.com/v0/meta/whoami"
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AirtablePersonalAccessToken
-}
-
-func (s Scanner) Description() string {
- return "Airtable is a cloud collaboration service that offers database-like features. Airtable OAuth tokens can be used to access and modify data within Airtable bases."
-}
diff --git a/pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken_integration_test.go b/pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken_integration_test.go
deleted file mode 100644
index 983ef217376b..000000000000
--- a/pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package airtablepersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAirtablepersonalaccesstoken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AIRTABLEPERSONALACCESSTOKEN")
- inactiveSecret := testSecrets.MustGetField("AIRTABLEPERSONALACCESSTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find an airtable secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirtablePersonalAccessToken,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find an airtable secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirtablePersonalAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airtablepersonalaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirtablePersonalAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airtablepersonalaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirtablePersonalAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Airtablepersonalaccesstoken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Airtablepersonalaccesstoken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken_test.go b/pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken_test.go
deleted file mode 100644
index 9a6138f90b38..000000000000
--- a/pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package airtablepersonalaccesstoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAirtablepersonalaccesstoken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the airtable API
- [DEBUG] Using Key=patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85
- [INFO] Response received: 200 OK
- `,
- want: []string{"patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {airtable}
- {airtable AQAAABAAA pat2kATFGrujqJTbT.e2082656c470902d83b47dc804e693df1deb30161affbda39d879a2cf44bef13}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"pat2kATFGrujqJTbT.e2082656c470902d83b47dc804e693df1deb30161affbda39d879a2cf44bef13"},
- },
- {
- name: "finds all matches",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using airtable Key=patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85
- [ERROR] Response received: 401 UnAuthorized
- [DEBUG] Using airtable Key=pat0VXr5I2HcapZE8.da2606afb7d97e936719ec952a4a18b44045e385d4ddf4f38dcc246fb63f0165
- [INFO] Response received: 200 OK
- `,
- want: []string{
- "patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85",
- "pat0VXr5I2HcapZE8.da2606afb7d97e936719ec952a4a18b44045e385d4ddf4f38dcc246fb63f0165",
- },
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the airtable API
- [DEBUG] Using Key=patfqpIZBPU6EAt5xe.458546d9c77b21f8a98141f2a403-d5626010f19efc16c20d57c4f41d44c8c85
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/airvisual/airvisual.go b/pkg/detectors/airvisual/airvisual.go
deleted file mode 100644
index 573e312fe72e..000000000000
--- a/pkg/detectors/airvisual/airvisual.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package airvisual
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"airvisual"}) + `\b([a-z0-9-]{36})\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{"airvisual"}
-}
-
-// FromData will find and optionally verify AirVisual secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AirVisual,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAirVisualKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAirVisualKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://api.airvisual.com/v2/countries?key=%s", key), nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Accept", "application/vnd.airvisual+json; version=3")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AirVisual
-}
-
-func (s Scanner) Description() string {
- return "AirVisual provides air quality information and monitoring. The API key allows access to various air quality data and services."
-}
diff --git a/pkg/detectors/airvisual/airvisual_integration_test.go b/pkg/detectors/airvisual/airvisual_integration_test.go
deleted file mode 100644
index 74af10ded93f..000000000000
--- a/pkg/detectors/airvisual/airvisual_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package airvisual
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAirVisual_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AIRVISUAL")
- inactiveSecret := testSecrets.MustGetField("AIRVISUAL_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airvisual secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirVisual,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a airvisual secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AirVisual,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AirVisual.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AirVisual.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/airvisual/airvisual_test.go b/pkg/detectors/airvisual/airvisual_test.go
deleted file mode 100644
index e3ff37e975b6..000000000000
--- a/pkg/detectors/airvisual/airvisual_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package airvisual
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAirVisual_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the airvisual API
- [DEBUG] Using Key=qscgyygcsq-wdvvok7slklklaasnd8afafxd
- [INFO] Response received: 200 OK
- `,
- want: []string{"qscgyygcsq-wdvvok7slklklaasnd8afafxd"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {airvisual}
- {airvisual AQAAABAAA rtcbsxiee3d5au8ik14g-8iqrsu8thl1pku8}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"rtcbsxiee3d5au8ik14g-8iqrsu8thl1pku8"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [DEBUG] airvisual api processing
- [INFO] Sending request to the API
- [DEBUG] Using Key=qscgyygcsq-wdvvok7slklklaasnd8afafxd
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the airvisual API
- [DEBUG] Using Key=wdvvok7slklklaasnd8afafxd
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/aiven/aiven.go b/pkg/detectors/aiven/aiven.go
deleted file mode 100644
index b72a1aeef0a0..000000000000
--- a/pkg/detectors/aiven/aiven.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package aiven
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"aiven"}) + `([a-zA-Z0-9/+=]{372})`)
-)
-
-// 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{"aiven"}
-}
-
-// FromData will find and optionally verify Aiven secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Aiven,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAivenKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAivenKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.aiven.io/v1/project", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("aivenv1 %s", key))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Aiven
-}
-
-func (s Scanner) Description() string {
- return "Aiven is a managed cloud service that provides various open-source data infrastructure services. Aiven API keys can be used to access and manage these services."
-}
diff --git a/pkg/detectors/aiven/aiven_integration_test.go b/pkg/detectors/aiven/aiven_integration_test.go
deleted file mode 100644
index 5939a17269d9..000000000000
--- a/pkg/detectors/aiven/aiven_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package aiven
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAiven_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AIVEN")
- inactiveSecret := testSecrets.MustGetField("AIVEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aiven secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Aiven,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aiven secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Aiven,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Aiven.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Aiven.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/aiven/aiven_test.go b/pkg/detectors/aiven/aiven_test.go
deleted file mode 100644
index d7087d0b762d..000000000000
--- a/pkg/detectors/aiven/aiven_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package aiven
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAiven_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the aiven API
- [DEBUG] Using Key = yb+Ygm82FfUworm2exB+Uk255p0uQKmmfx4ut1KfsZ3YI3Gp2xPYyxZgrwYabMxXXO4WPsK7xlLJRy0BWIpM2SKnzA2p69P8aOmYbl24ZiVGlLXyQxeVDDy7gru5Yzt=Y1UDLBpsW=hhGIKsrPgc/7hpxuEfEqbXJe5IBYO484F+ekaTmYN4nTF94O==3WuG+WuSW7zaYzXH1V==kZFj07zBtmShS0z/lW=N3HipH=oJjXI2pyFxU+A7vM9yHdUHoiZEOVoWsyp5zO1ajBOqFr=3jIIaXWmbH33dP2ZNQFJhqbeg6JlXA9GpfMFht5=ZCC1IirWCNp=UILbmZtvu9d2M8U0YNHwAGKtjrPS5lZvAU+W5s2Ti
- [INFO] Response received: 200 OK
- `,
- want: []string{"yb+Ygm82FfUworm2exB+Uk255p0uQKmmfx4ut1KfsZ3YI3Gp2xPYyxZgrwYabMxXXO4WPsK7xlLJRy0BWIpM2SKnzA2p69P8aOmYbl24ZiVGlLXyQxeVDDy7gru5Yzt=Y1UDLBpsW=hhGIKsrPgc/7hpxuEfEqbXJe5IBYO484F+ekaTmYN4nTF94O==3WuG+WuSW7zaYzXH1V==kZFj07zBtmShS0z/lW=N3HipH=oJjXI2pyFxU+A7vM9yHdUHoiZEOVoWsyp5zO1ajBOqFr=3jIIaXWmbH33dP2ZNQFJhqbeg6JlXA9GpfMFht5=ZCC1IirWCNp=UILbmZtvu9d2M8U0YNHwAGKtjrPS5lZvAU+W5s2Ti"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {aiven}
- {aiven AQAAABAAA IGhXNR6g7rogABp/H2iDQu7TgkXpvn9KnwzJfeh+8p7M=JVsI2QoQ38mmQHt450bQC4wBOGFhV+9QT2KGWSMfTOxTUrUXygaLlwsXo/RBxKXyOdh=/L8EGGrqG6=qbd0UzDAfc0xeAfXd30RGj+Ypsrrvdda=ZPa32BBID5r2ClfJSbgpfWIpVC1b5vlqCdy5LIWABZJzjBC5VweqZ04XFaCh+15NuSQ4E0KdGwPdkrfxxjY20I1wDvlKxzxL7dfCly3KVlQv7KBEFSLaLRNRocPYToUXqU4yAXKvXf03K=k1mahpxFUp94c35k/n055LVs=xbyL6AKdW=sCCa1AFIYKBDMBprTsZ6Al7DHx=XA6qLNWYxS7}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"IGhXNR6g7rogABp/H2iDQu7TgkXpvn9KnwzJfeh+8p7M=JVsI2QoQ38mmQHt450bQC4wBOGFhV+9QT2KGWSMfTOxTUrUXygaLlwsXo/RBxKXyOdh=/L8EGGrqG6=qbd0UzDAfc0xeAfXd30RGj+Ypsrrvdda=ZPa32BBID5r2ClfJSbgpfWIpVC1b5vlqCdy5LIWABZJzjBC5VweqZ04XFaCh+15NuSQ4E0KdGwPdkrfxxjY20I1wDvlKxzxL7dfCly3KVlQv7KBEFSLaLRNRocPYToUXqU4yAXKvXf03K=k1mahpxFUp94c35k/n055LVs=xbyL6AKdW=sCCa1AFIYKBDMBprTsZ6Al7DHx=XA6qLNWYxS7"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [DEBUG] aiven api processing
- [INFO] Sending request to the API
- [DEBUG] Using Key=yb+Ygm82FfUworm2exB+Uk255p0uQKmmfx4ut1KfsZ3YI3Gp2xPYyxZgrwYabMxXXO4WPsK7xlLJRy0BWIpM2SKnzA2p69P8aOmYbl24ZiVGlLXyQxeVDDy7gru5Yzt=Y1UDLBpsW=hhGIKsrPgc/7hpxuEfEqbXJe5IBYO484F+ekaTmYN4nTF94O==3WuG+WuSW7zaYzXH1V==kZFj07zBtmShS0z/lW=N3HipH=oJjXI2pyFxU+A7vM9yHdUHoiZEOVoWsyp5zO1ajBOqFr=3jIIaXWmbH33dP2ZNQFJhqbeg6JlXA9GpfMFht5=ZCC1IirWCNp=UILbmZtvu9d2M8U0YNHwAGKtjrPS5lZvAU+W5s2Ti
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the aiven API
- [DEBUG] Using Key=SSs8PGhwqWzb4qfqiwLV/bNHfiQ2VSKyX88AAYm3+CGHbTe/FYXRNOYYHO=PXwuL/GftiES7j8ffzWW9p1dAyNc6hZZpoazmd+Vf1kbukZSL8QO/LdKFI/YFlupu0dELqQVHeZi/cJlnp6aQeY7zIJiHhJS51ZVdOamc=zOUMebry3BYOo2LhYIz+mLND7s5/cHZZpkEvTXrKnVf4vdYMl+fawv84AYCTo9pry8FQBsqRex2HL98kAiqhVYG+nLyRz/hZCo8owaRkzli1BUT4O63TSKJIgnECOBvyZz7o+yX92BhDe+B2Tllk3y2=qG5TiEl2sCJI8V5GJ1cz52RpXx2hVXMi=1Zl5CHpX8Adr9VMbj$Co
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/alchemy/alchemy.go b/pkg/detectors/alchemy/alchemy.go
deleted file mode 100644
index 7084749f7e62..000000000000
--- a/pkg/detectors/alchemy/alchemy.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package alchemy
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"alchemy"}) + `\b([0-9a-zA-Z_]{32}|alcht_[0-9a-zA-Z]{30})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"alchemy", "alcht_"}
-}
-
-// FromData will find and optionally verify Alchemy secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Alchemy,
- Raw: []byte(match),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://eth-mainnet.g.alchemy.com/v2/"+token+"/getNFTs/?owner=vitalik.eth", nil)
- if err != nil {
- return false, nil, err
- }
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- // If the endpoint returns useful information, we can return it as a map.
- return true, nil, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Alchemy
-}
-
-func (s Scanner) Description() string {
- return "Alchemy is a blockchain development platform that provides a suite of tools and services for building and scaling decentralized applications. Alchemy API keys can be used to access these services."
-}
diff --git a/pkg/detectors/alchemy/alchemy_integration_test.go b/pkg/detectors/alchemy/alchemy_integration_test.go
deleted file mode 100644
index 036e0a39d579..000000000000
--- a/pkg/detectors/alchemy/alchemy_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package alchemy
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAlchemy_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ALCHEMY")
- inactiveSecret := testSecrets.MustGetField("ALCHEMY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alchemy secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Alchemy,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alchemy secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Alchemy,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alchemy secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Alchemy,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alchemy secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Alchemy,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Alchemy.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Alchemy.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/alchemy/alchemy_test.go b/pkg/detectors/alchemy/alchemy_test.go
deleted file mode 100644
index ce397c3635be..000000000000
--- a/pkg/detectors/alchemy/alchemy_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package alchemy
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAlchemy_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the alchemy API
- [DEBUG] Using Key=alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D
- [INFO] Response received: 200 OK
- `,
- want: []string{"alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {alchemy}
- {alchemy AQAAABAAA 5iqW7gKQVXvwnykF9xAVfenemmnUJznI}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"5iqW7gKQVXvwnykF9xAVfenemmnUJznI"},
- },
- {
- name: "finds all matches",
- input: `
- [INFO] Sending request to the alchemy API
- [DEBUG] Using Key=alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D
- [ERROR] Response received 401 UnAuthorized
- [DEBUG] Using alchemy Key=xuQIeWFVEp8k8Uu9FwPx6X5C8IViOe1o
- [INFO] Response received: 200 OK
- `,
- want: []string{"alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D", "xuQIeWFVEp8k8Uu9FwPx6X5C8IViOe1o"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the alchemy API
- [DEBUG] Using Key=alcht_a2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/alconost/alconost.go b/pkg/detectors/alconost/alconost.go
deleted file mode 100644
index b183b59f2b6d..000000000000
--- a/pkg/detectors/alconost/alconost.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package alconost
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"alconost"}) + `\b([0-9Aa-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"alconost"}
-}
-
-// FromData will find and optionally verify Alconost secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Alconost,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAlconostKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAlconostKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- data := fmt.Sprintf("%s:", key)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://nitro.alconost.com/api/v1/account", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Alconost
-}
-
-func (s Scanner) Description() string {
- return "Alconost is a translation and localization service. Alconost API keys can be used to access and modify translation data."
-}
diff --git a/pkg/detectors/alconost/alconost_integration_test.go b/pkg/detectors/alconost/alconost_integration_test.go
deleted file mode 100644
index 6e3fda8fde88..000000000000
--- a/pkg/detectors/alconost/alconost_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package alconost
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAlconost_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ALCONOST")
- inactiveSecret := testSecrets.MustGetField("ALCONOST_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alconost secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Alconost,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alconost secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Alconost,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Alconost.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Alconost.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/alconost/alconost_test.go b/pkg/detectors/alconost/alconost_test.go
deleted file mode 100644
index f101775e5f5e..000000000000
--- a/pkg/detectors/alconost/alconost_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package alconost
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAlconost_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the alconost API
- [DEBUG] Using Key=wdvnousa87acfxp9ioasrea4tbeasrfa
- [INFO] Response received: 200 OK
- `,
- want: []string{"wdvnousa87acfxp9ioasrea4tbeasrfa"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {alconost}
- {alconost AQAAABAAA Awxzhkwff46dtkt5pnvdlss6t2kA44a7}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"Awxzhkwff46dtkt5pnvdlss6t2kA44a7"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [DEBUG] alconost api processing
- [INFO] Sending request to the API
- [DEBUG] Using Key=wdvnousa87acfxp9ioasrea4tbeasrfa
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the alconost API
- [DEBUG] Using Key=wdvnousa87acfxp9ioasra4tBeasrfa
- [INFO] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/alegra/alegra.go b/pkg/detectors/alegra/alegra.go
deleted file mode 100644
index 856d77237681..000000000000
--- a/pkg/detectors/alegra/alegra.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package alegra
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"alegra"}) + `\b([a-z0-9-]{20})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"alegra"}) + common.EmailPattern)
-)
-
-// 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{"alegra"}
-}
-
-// FromData will find and optionally verify Alegra secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- uniqueTokens := make(map[string]struct{})
- uniqueIDs := make(map[string]struct{})
-
- for _, match := range keyMatches {
- uniqueTokens[match[1]] = struct{}{}
- }
-
- for _, match := range idMatches {
- id := match[0][strings.LastIndex(match[0], " ")+1:]
- uniqueIDs[id] = struct{}{}
- }
-
- for token := range uniqueTokens {
- for id := range uniqueIDs {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Alegra,
- Raw: []byte(token),
- RawV2: []byte(token + ":" + id),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyCredentials(ctx, client, id, token)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, token)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyCredentials(ctx context.Context, client *http.Client, username, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.alegra.com/api/v1/users/self", nil)
- if err != nil {
- return false, nil
- }
- req.SetBasicAuth(username, token)
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Alegra
-}
-
-func (s Scanner) Description() string {
- return "Alegra is a cloud-based accounting software. Alegra API keys can be used to access and modify accounting data and user information."
-}
diff --git a/pkg/detectors/alegra/alegra_integration_test.go b/pkg/detectors/alegra/alegra_integration_test.go
deleted file mode 100644
index 35961518bcb9..000000000000
--- a/pkg/detectors/alegra/alegra_integration_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package alegra
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAlegra_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ALEGRA")
- id := testSecrets.MustGetField("ACCOUNT_USER")
- inactiveSecret := testSecrets.MustGetField("ALEGRA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alegra secret %s within alegra %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Alegra,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alegra secret %s within alegra %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Alegra,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Alegra.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Alegra.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/alegra/alegra_test.go b/pkg/detectors/alegra/alegra_test.go
deleted file mode 100644
index cf35bbda3f25..000000000000
--- a/pkg/detectors/alegra/alegra_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package alegra
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAlegra_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using alegra Key=wdvn-usa87a-fxp9ioas
- [DEBUG] Using alegra Email = testUser.1005@example.com
- [INFO] Response received: 200 OK
- `,
- want: []string{"wdvn-usa87a-fxp9ioas:testUser.1005@example.com"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {alegra kk18@example.com}
- {alegra AQAAABAAA buihlmkfnh5m1lk5z6do}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"buihlmkfnh5m1lk5z6do:kk18@example.com"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [DEBUG] alegra api processing
- [INFO] Sending request to the API
- [DEBUG] Using Key=wdvn-usa87a-fxp9ioas
- [DEBUG] Using Email=testUser.1005@example.com
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using alegra Key=wdvn_usa87a-fxp9ioas
- [DEBUG] Using alegra Email=testUser.1005@example.com
- [INFO] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/aletheiaapi/aletheiaapi.go b/pkg/detectors/aletheiaapi/aletheiaapi.go
deleted file mode 100644
index 550bf9b08a42..000000000000
--- a/pkg/detectors/aletheiaapi/aletheiaapi.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package aletheiaapi
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"aletheiaapi"}) + `\b([A-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"aletheiaapi"}
-}
-
-// FromData will find and optionally verify AletheiaApi secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AletheiaApi,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAletheiaAPIKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAletheiaAPIKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- timeout := 10 * time.Second
- client.Timeout = timeout
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.aletheiaapi.com/StockData?symbol=msft&summary=true", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Key", key)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AletheiaApi
-}
-
-func (s Scanner) Description() string {
- return "AletheiaApi is a service providing financial data. AletheiaApi keys can be used to access this data."
-}
diff --git a/pkg/detectors/aletheiaapi/aletheiaapi_integration_test.go b/pkg/detectors/aletheiaapi/aletheiaapi_integration_test.go
deleted file mode 100644
index 32b39a124fc8..000000000000
--- a/pkg/detectors/aletheiaapi/aletheiaapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package aletheiaapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAletheiaApi_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ALETHEIAAPI")
- inactiveSecret := testSecrets.MustGetField("ALETHEIAAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aletheiaapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AletheiaApi,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aletheiaapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AletheiaApi,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AletheiaApi.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AletheiaApi.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/aletheiaapi/aletheiaapi_test.go b/pkg/detectors/aletheiaapi/aletheiaapi_test.go
deleted file mode 100644
index 46f99e433c43..000000000000
--- a/pkg/detectors/aletheiaapi/aletheiaapi_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package aletheiaapi
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAleTheIaAPI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the aletheiaapi
- [DEBUG] Using Key=LY027C40U2KNNZLFO1WEU3XQZ13LW515
- [INFO] Response received: 200 OK
- `,
- want: []string{"LY027C40U2KNNZLFO1WEU3XQZ13LW515"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {aletheiaapi}
- {aletheiaapi AQAAABAAA K7SOW2B8QH9QE435NLH07PH22XL4YOPG}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"K7SOW2B8QH9QE435NLH07PH22XL4YOPG"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [DEBUG] aletheiaapi api processing
- [INFO] Sending request to the API
- [DEBUG] Using Key=LY027C40U2KNNZLFO1WEU3XQZ13LW515
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the aletheiaapi
- [DEBUG] Using Key=LY027c40U2KNNZLFO1WEU3XQZ13LW515
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/algoliaadminkey/algoliaadminkey.go b/pkg/detectors/algoliaadminkey/algoliaadminkey.go
deleted file mode 100644
index 73320dec62e8..000000000000
--- a/pkg/detectors/algoliaadminkey/algoliaadminkey.go
+++ /dev/null
@@ -1,188 +0,0 @@
-package algoliaadminkey
-
-import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "slices"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"algolia", "docsearch", "appId"}) + `\b([A-Z0-9]{10})\b`)
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"algolia", "docsearch", "apiKey"}) + `\b([a-zA-Z0-9]{32})\b`)
-
- invalidHosts = simple.NewCache[struct{}]()
-
- errNoHost = errors.New("no such host")
-)
-
-// 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{"algolia", "docsearch"}
-}
-
-// FromData will find and optionally verify AlgoliaAdminKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- logger := logContext.AddLogger(ctx).Logger().WithName("algoliaadminkey")
- dataStr := string(data)
-
- // Deduplicate matches.
- idMatches := make(map[string]struct{})
- for _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {
- id := match[1]
- if detectors.StringShannonEntropy(id) > 2 {
- idMatches[id] = struct{}{}
- }
- }
- keyMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- key := match[1]
- if detectors.StringShannonEntropy(key) > 3 {
- keyMatches[key] = struct{}{}
- }
- }
-
- // Test matches.
- for key := range keyMatches {
- for id := range idMatches {
- if invalidHosts.Exists(id) {
- logger.V(3).Info("Skipping application id: no such host", "host", id)
- delete(idMatches, id)
- continue
- }
-
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_AlgoliaAdminKey,
- Raw: []byte(key),
- RawV2: []byte(id + ":" + key),
- }
-
- if verify {
- // Verify if the key is a valid Algolia Admin Key.
- isVerified, extraData, verificationErr := verifyMatch(ctx, id, key)
- r.Verified = isVerified
- r.ExtraData = extraData
- if verificationErr != nil {
- if errors.Is(verificationErr, errNoHost) {
- invalidHosts.Set(id, struct{}{})
- continue
- }
-
- r.SetVerificationError(verificationErr, key)
- }
- }
-
- results = append(results, r)
- if r.Verified {
- break
- }
- }
- }
- return results, nil
-}
-
-// https://www.algolia.com/doc/guides/security/api-keys/#access-control-list-acl
-var nonSensitivePermissions = map[string]struct{}{
- "listIndexes": {},
- "search": {},
- "settings": {},
-}
-
-func verifyMatch(ctx context.Context, appId, apiKey string) (bool, map[string]string, error) {
- // https://www.algolia.com/doc/rest-api/search/#section/Base-URLs
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+appId+".algolia.net/1/keys/"+apiKey, nil)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Set("X-Algolia-Application-Id", appId)
- req.Header.Set("X-Algolia-API-Key", apiKey)
-
- res, err := client.Do(req)
- if err != nil {
- // lookup xyz.algolia.net: no such host
- if strings.Contains(err.Error(), "no such host") {
- return false, nil, errNoHost
- }
-
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- var keyRes keyResponse
- if err := json.NewDecoder(res.Body).Decode(&keyRes); err != nil {
- return false, nil, err
- }
-
- // Check if the key has sensitive permissions, even if it's not an Admin Key.
- hasSensitivePerms := false
- for _, acl := range keyRes.ACL {
- if _, ok := nonSensitivePermissions[acl]; !ok {
- hasSensitivePerms = true
- break
- }
- }
- if !hasSensitivePerms {
- return false, nil, nil
- }
-
- slices.Sort(keyRes.ACL)
- extraData := map[string]string{
- "acl": strings.Join(keyRes.ACL, ","),
- }
- if keyRes.Description != "" && keyRes.Description != "" {
- extraData["description"] = keyRes.Description
- }
- return true, extraData, nil
- case http.StatusUnauthorized:
- return false, nil, nil
- case http.StatusForbidden:
- // Invalidated key.
- // {"message":"Invalid Application-ID or API key","status":403}
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-// https://www.algolia.com/doc/rest-api/search/#tag/Api-Keys/operation/getApiKey
-type keyResponse struct {
- ACL []string `json:"acl"`
- Description string `json:"description"`
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AlgoliaAdminKey
-}
-
-func (s Scanner) Description() string {
- return "Algolia is a search-as-a-service platform. Algolia Admin Keys can be used to manage indices and API keys, and perform administrative tasks."
-}
diff --git a/pkg/detectors/algoliaadminkey/algoliaadminkey_integration_test.go b/pkg/detectors/algoliaadminkey/algoliaadminkey_integration_test.go
deleted file mode 100644
index a5fe493f280b..000000000000
--- a/pkg/detectors/algoliaadminkey/algoliaadminkey_integration_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package algoliaadminkey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAlgoliaAdminKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ALGOLIAADMINKEY_TOKEN")
- inactiveSecret := testSecrets.MustGetField("ALGOLIAADMINKEY_INACTIVE")
- id := testSecrets.MustGetField("ALGOLIAADMINKEY_APPID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a algolia secret %s within algolia %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AlgoliaAdminKey,
- Verified: true,
- RawV2: []byte(fmt.Sprintf("%s%s", secret, id)),
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a algolia secret %s within algolia %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AlgoliaAdminKey,
- Verified: false,
- RawV2: []byte(fmt.Sprintf("%s%s", inactiveSecret, id)),
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AlgoliaAdminKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AlgoliaAdminKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/algoliaadminkey/algoliaadminkey_test.go b/pkg/detectors/algoliaadminkey/algoliaadminkey_test.go
deleted file mode 100644
index e3dd01411f2e..000000000000
--- a/pkg/detectors/algoliaadminkey/algoliaadminkey_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package algoliaadminkey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAlgoliaAdminKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using algolia Key=BsDaN7ZU7kFiUX5CpN8CUf3nkMaSeZYn
- [DEBUG] Using docsearch ID=844XQV5SUA
- [INFO] Response received: 200 OK
- `,
- want: []string{"844XQV5SUA:BsDaN7ZU7kFiUX5CpN8CUf3nkMaSeZYn"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {appId 0VJ9I1WV78}
- {algolia AQAAABAAA 4AYm3wz7nfnX7Bqtw5e5Qo3Z5vfBe0eS}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"0VJ9I1WV78:4AYm3wz7nfnX7Bqtw5e5Qo3Z5vfBe0eS"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [INFO] Sending request to the algolia API
- [DEBUG] Using Key=BsDaN7ZU7kFiUX5CpN8CUf3nkMaSeZYn
- [DEBUG] Using ID=844XQV5SUA
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using algolia Key=BsD-N7ZU7kFiUX5CpN8CUf3nkMaSeZYn
- [DEBUG] Using docsearch ID=844XqV5SUA
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/alibaba/alibaba.go b/pkg/detectors/alibaba/alibaba.go
deleted file mode 100644
index b9e4727f1482..000000000000
--- a/pkg/detectors/alibaba/alibaba.go
+++ /dev/null
@@ -1,179 +0,0 @@
-package alibaba
-
-import (
- "context"
- "crypto/hmac"
- "crypto/rand"
- "crypto/sha1"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
-}
-
-type alibabaResp struct {
- RequestId string `json:"RequestId"`
- Message string `json:"Message"`
- Recommend string `json:"Recommend"`
- HostId string `json:"HostId"`
- Code string `json:"Code"`
-}
-
-const alibabaURL = "https://ecs.aliyuncs.com"
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
-
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b([a-zA-Z0-9]{30})\b`)
- idPat = regexp.MustCompile(`\b(LTAI[a-zA-Z0-9]{17,21})[\"';\s]*`)
-)
-
-// 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{"LTAI"}
-}
-
-func (s Scanner) Description() string {
- return "Alibaba Cloud is a cloud computing service that provides a suite of cloud computing services including data storage, relational databases, big-data processing, and content delivery networks (CDNs). Alibaba Cloud API keys can be used to access and manage these services."
-}
-
-func randString(n int) string {
- const alphanum = "0123456789abcdefghijklmnopqrstuvwxyz"
- var bytes = make([]byte, n)
- _, _ = rand.Read(bytes)
- for i, b := range bytes {
- bytes[i] = alphanum[b%byte(len(alphanum))]
- }
- return string(bytes)
-}
-
-func GetSignature(input, key string) string {
- key_for_sign := []byte(key)
- h := hmac.New(sha1.New, key_for_sign)
- h.Write([]byte(input))
- return base64.StdEncoding.EncodeToString(h.Sum(nil))
-}
-
-func buildStringToSign(method, input string) string {
- filter := strings.Replace(input, "+", "%20", -1)
- filter = strings.Replace(filter, "%7E", "~", -1)
- filter = strings.Replace(filter, "*", "%2A", -1)
- filter = method + "&%2F&" + url.QueryEscape(filter)
- return filter
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Alibaba secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- resIdMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Alibaba,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyAlibaba(ctx, client, resIdMatch, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyAlibaba(ctx context.Context, client *http.Client, resIdMatch, resMatch string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, alibabaURL, nil)
- if err != nil {
- return false, err
- }
-
- dateISO := time.Now().UTC().Format("2006-01-02T15:04:05Z07:00")
- params := req.URL.Query()
- params.Add("AccessKeyId", resIdMatch)
- params.Add("Action", "DescribeRegions")
- params.Add("Format", "JSON")
- params.Add("SignatureMethod", "HMAC-SHA1")
- params.Add("SignatureNonce", randString(16))
- params.Add("SignatureVersion", "1.0")
- params.Add("Timestamp", dateISO)
- params.Add("Version", "2014-05-26")
-
- stringToSign := buildStringToSign(req.Method, params.Encode())
- signature := GetSignature(stringToSign, resMatch+"&") // Get Signature HMAC SHA1
- params.Add("Signature", signature)
- req.URL.RawQuery = params.Encode()
-
- req.Header.Add("Content-Type", "text/xml;charset=utf-8")
- req.Header.Add("Content-Length", strconv.Itoa(len(params.Encode())))
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- var alibabaResp alibabaResp
- if err = json.NewDecoder(res.Body).Decode(&alibabaResp); err != nil {
- return false, err
- }
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusNotFound, http.StatusBadRequest:
- // 400 used for most of error cases
- // 404 used if the AccessKeyId is not valid
- return false, nil
- default:
- err := fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- if alibabaResp.Message != "" {
- err = fmt.Errorf("%s: %s, %s", err, alibabaResp.Message, alibabaResp.Code)
- }
- return false, err
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Alibaba
-}
diff --git a/pkg/detectors/alibaba/alibaba_integration_test.go b/pkg/detectors/alibaba/alibaba_integration_test.go
deleted file mode 100644
index 31b81881e0e7..000000000000
--- a/pkg/detectors/alibaba/alibaba_integration_test.go
+++ /dev/null
@@ -1,186 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package alibaba
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAlibaba_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ALIBABA_SECRET")
- inactiveSecret := testSecrets.MustGetField("ALIBABA_SECRET_INACTIVE")
- id := testSecrets.MustGetField("ALIBABA_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alibaba secret %s within alibaba %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Alibaba,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alibaba secret %s within alibaba %s", secret, id)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Alibaba,
- Verified: false,
- }
- r.SetVerificationError(context.DeadlineExceeded)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "{}")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alibaba secret %s within alibaba %s", secret, id)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Alibaba,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 500"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, real secrets, verification error due to broken json",
- s: Scanner{client: common.ConstantResponseHttpClient(418, "{")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alibaba secret %s within alibaba %s", secret, id)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Alibaba,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected EOF"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alibaba secret %s within alibaba %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Alibaba,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Alibaba.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("Alibaba.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/alibaba/alibaba_test.go b/pkg/detectors/alibaba/alibaba_test.go
deleted file mode 100644
index 016bf20d546f..000000000000
--- a/pkg/detectors/alibaba/alibaba_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package alibaba
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAliBaba_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using Key=CwgR2UwgaWd7hgUdQkwFnK9vvEeO4R
- [DEBUG] Using ID=LTAIXgRPqwF1DhBf6Q1uZ5DrM
- [INFO] Response received: 200 OK
- `,
- want: []string{"CwgR2UwgaWd7hgUdQkwFnK9vvEeO4RLTAIXgRPqwF1DhBf6Q1uZ5DrM"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {WX6OtM8pbcrXWMIGc5evYousFWBlBm}
- {AQAAABAAA LTAImg3ZeAPatbAtEDS9HVZ}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"WX6OtM8pbcrXWMIGc5evYousFWBlBmLTAImg3ZeAPatbAtEDS9HVZ"},
- },
- {
- name: "valid pattern - ignore special characters at end",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using Key=CwgR2UwgaWd7hgUdQkwFnK9vvEeO4R
- [DEBUG] Using ID=LTAIXgRPqwF1DhBf6Q1uZ5DrM;
- [INFO] Response received: 200 OK
- `,
- want: []string{"CwgR2UwgaWd7hgUdQkwFnK9vvEeO4RLTAIXgRPqwF1DhBf6Q1uZ5DrM"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using Key=CwgR2UwgaWd7hgUdQkwFnK9vvEeO4
- [DEBUG] Using ID=LTAIXgRPqwF1DhBf6Q1uZ5DrMYPW
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/alienvault/alienvault.go b/pkg/detectors/alienvault/alienvault.go
deleted file mode 100644
index aa4452730490..000000000000
--- a/pkg/detectors/alienvault/alienvault.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package alienvault
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"alienvault"}) + `\b([a-z0-9]{64})\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{"alienvault"}
-}
-
-// FromData will find and optionally verify AlienVault secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AlienVault,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAlienVaultKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AlienVault
-}
-
-func verifyAlienVaultKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://otx.alienvault.com/api/v1/users/me", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("X-OTX-API-KEY", key)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Description() string {
- return "AlienVault is a threat intelligence platform providing real-time data on emerging threats. AlienVault API keys can be used to access threat data and other services."
-}
diff --git a/pkg/detectors/alienvault/alienvault_integration_test.go b/pkg/detectors/alienvault/alienvault_integration_test.go
deleted file mode 100644
index ab9e035bdf94..000000000000
--- a/pkg/detectors/alienvault/alienvault_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package alienvault
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAlienVault_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ALIENVAULT")
- inactiveSecret := testSecrets.MustGetField("ALIENVAULT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alienvault secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AlienVault,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a alienvault secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AlienVault,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AlienVault.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AlienVault.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/alienvault/alienvault_test.go b/pkg/detectors/alienvault/alienvault_test.go
deleted file mode 100644
index a14f84652e10..000000000000
--- a/pkg/detectors/alienvault/alienvault_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package alienvault
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAlienVault_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the alienvault API
- [DEBUG] Using Key=3em7p52ec9ut4k9ccqha19rz3oyeqnij3mn3ivml577f8pb2179yz9totr648hmy
- [INFO] Response received: 200 OK
- `,
- want: []string{"3em7p52ec9ut4k9ccqha19rz3oyeqnij3mn3ivml577f8pb2179yz9totr648hmy"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {alienvault}
- {AQAAABAAA xyi7bj56t5b0hkinw4vz8qgffqhfb2ypemdnt407bke6s0ouuswvcdf5c1qpvse0}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"xyi7bj56t5b0hkinw4vz8qgffqhfb2ypemdnt407bke6s0ouuswvcdf5c1qpvse0"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [INFO] Fetching data from alienvault
- [INFO] Sending request to the API
- [DEBUG] Using Key=3em7p52ec9ut4k9ccqha19rz3oyeqnij3mn3ivml577f8pb2179yz9totr648hmy
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the alienvault API
- [DEBUG] Using Key=3em7p52ec9ut4k9ccqha19rz3o_eqnij3mn3ivml577f8pb2179yz9totr648hmy
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/allsports/allsports.go b/pkg/detectors/allsports/allsports.go
deleted file mode 100644
index 348f92be6bdb..000000000000
--- a/pkg/detectors/allsports/allsports.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package allsports
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"allsports"}) + `\b([0-9a-z]{64})\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{"allsports"}
-}
-
-// FromData will find and optionally verify Allsports secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Allsports,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAllSportsKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAllSportsKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://apiv2.allsportsapi.com/football/?met=Countries&APIkey="+key, nil)
- if err != nil {
- return false, err
- }
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
- body := string(bodyBytes)
- if strings.Contains(body, "Wrong login credentials") {
- return false, nil
- }
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Allsports
-}
-
-func (s Scanner) Description() string {
- return "Allsports API keys can be used to access and interact with the Allsports API, allowing retrieval of sports data and other related operations."
-}
diff --git a/pkg/detectors/allsports/allsports_integration_test.go b/pkg/detectors/allsports/allsports_integration_test.go
deleted file mode 100644
index 0619c62f7369..000000000000
--- a/pkg/detectors/allsports/allsports_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package allsports
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAllsports_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ALLSPORTS")
- inactiveSecret := testSecrets.MustGetField("ALLSPORTS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a allsports secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Allsports,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a allsports secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Allsports,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Allsports.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Allsports.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/allsports/allsports_test.go b/pkg/detectors/allsports/allsports_test.go
deleted file mode 100644
index e16a5dbaf15c..000000000000
--- a/pkg/detectors/allsports/allsports_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package allsports
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAllSports_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the allsports API
- [DEBUG] Using Key=cq73u5azj3p3shfvzz3lw1typfqu6uduq7bophtq4veta7cnvd4s5htkb8lgk4vr
- [INFO] Response received: 200 OK
- `,
- want: []string{"cq73u5azj3p3shfvzz3lw1typfqu6uduq7bophtq4veta7cnvd4s5htkb8lgk4vr"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {allsports}
- {AQAAABAAA bj8yzu3awie5akwiwcb7esqygqx14gt65j9lrcpec0v28ckkswtyza1x9747gap5}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"bj8yzu3awie5akwiwcb7esqygqx14gt65j9lrcpec0v28ckkswtyza1x9747gap5"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [DEBUG] allsports api processing
- [INFO] Sending request to the API
- [DEBUG] Using Key=cq73u5azj3p3shfvzz3lw1typfqu6uduq7bophtq4veta7cnvd4s5htkb8lgk4vr
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the allsports API
- [DEBUG] Using Key=d1f2e3c4b5a6d7e8f9G0h1i2j3k4l5m6n7o8p9q0r1s2t3u4v5w6x7y8z9a0b1ce
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/amadeus/amadeus.go b/pkg/detectors/amadeus/amadeus.go
deleted file mode 100644
index ad68b77e074d..000000000000
--- a/pkg/detectors/amadeus/amadeus.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package amadeus
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"amadeus"}) + `\b([0-9A-Za-z]{32})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"amadeus"}) + `\b([0-9A-Za-z]{16})\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{"amadeus"}
-}
-
-// FromData will find and optionally verify Amadeus secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys, uniqueSecrets = make(map[string]struct{}), make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for _, matches := range secretPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueSecrets[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- for secret := range uniqueSecrets {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Amadeus,
- Raw: []byte(key),
- RawV2: []byte(key + secret),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAdobeIOSecret(ctx, client, key, secret)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyAdobeIOSecret(ctx context.Context, client *http.Client, key string, secret string) (bool, error) {
- payload := strings.NewReader("grant_type=client_credentials&client_id=" + key + "&client_secret=" + secret)
-
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://test.api.amadeus.com/v1/security/oauth2/token", payload)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
- body := string(bodyBytes)
- if !strings.Contains(body, "access_token") {
- return false, nil
- }
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Amadeus
-}
-
-func (s Scanner) Description() string {
- return "Amadeus provides travel technology solutions. Amadeus API keys can be used to access and modify travel-related data and services."
-}
diff --git a/pkg/detectors/amadeus/amadeus_integration_test.go b/pkg/detectors/amadeus/amadeus_integration_test.go
deleted file mode 100644
index 0f87057ca491..000000000000
--- a/pkg/detectors/amadeus/amadeus_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package amadeus
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAmadeus_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- id := testSecrets.MustGetField("AMADEUS")
- secret := testSecrets.MustGetField("AMADEUS_SECRET")
- inactiveSecret := testSecrets.MustGetField("AMADEUS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a amadeus secret %s within amadeus id %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Amadeus,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a amadeus secret %s within amadeus id %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Amadeus,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Amadeus.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Amadeus.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/amadeus/amadeus_test.go b/pkg/detectors/amadeus/amadeus_test.go
deleted file mode 100644
index 0c075b8a1d47..000000000000
--- a/pkg/detectors/amadeus/amadeus_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package amadeus
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAmadeus_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using amadeus Key=ttdveNai3Gj6Zrjvgz4fyBEWRLARCG6a
- [DEBUG] Using amadeus Secret=9wqrSr2qveaqgQns
- [INFO] Response received: 200 OK
- `,
- want: []string{"ttdveNai3Gj6Zrjvgz4fyBEWRLARCG6a9wqrSr2qveaqgQns"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {amadeus ey6U46qCx26dqzMVWAGiibt6m65mM5w9}
- {amadeus AQAAABAAA Ew3TfmLHYaRjPnYO}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"ey6U46qCx26dqzMVWAGiibt6m65mM5w9Ew3TfmLHYaRjPnYO"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [INFO] Sending request to the amadeus API
- [DEBUG] Using Key=ttdveNai3Gj6Zrjvgz4fyBEWRLARCG6a
- [DEBUG] Using Secret=9wqrSr2qveaqgQns
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the amadeus API
- [DEBUG] Using amadeus Key=tthdveNai3Gj6Zrjvgz4fyBEWRLARCG6a
- [DEBUG] Using amadeus Secret=9wqrSr2qveacqgQns
- [INFO] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/ambee/ambee.go b/pkg/detectors/ambee/ambee.go
deleted file mode 100644
index 82d8637c8d64..000000000000
--- a/pkg/detectors/ambee/ambee.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package ambee
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ambee"}) + `\b([0-9a-f]{64})\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{"ambee"}
-}
-
-// FromData will find and optionally verify Ambee secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Ambee,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAmbeeKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAmbeeKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.ambeedata.com/latest/by-lat-lng?lat=12&lng=77", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("x-api-key", key)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Ambee
-}
-
-func (s Scanner) Description() string {
- return "Ambee provides environmental and climate data APIs. Ambee API keys can be used to access this data for various applications such as weather forecasting, air quality monitoring, and more."
-}
diff --git a/pkg/detectors/ambee/ambee_integration_test.go b/pkg/detectors/ambee/ambee_integration_test.go
deleted file mode 100644
index 6ff21de9dc28..000000000000
--- a/pkg/detectors/ambee/ambee_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package ambee
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAmbee_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AMBEE")
- inactiveSecret := testSecrets.MustGetField("AMBEE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ambee secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Ambee,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ambee secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Ambee,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Ambee.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Ambee.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/ambee/ambee_test.go b/pkg/detectors/ambee/ambee_test.go
deleted file mode 100644
index 42ad9e06dc04..000000000000
--- a/pkg/detectors/ambee/ambee_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package ambee
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAmbee_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the ambee API
- [DEBUG] Using Key=eccb41cc2d4dab96b748ed040e9b308161279820447ef4553ba6e6d20ecb9962
- [INFO] Response received: 200 OK
- `,
- want: []string{"eccb41cc2d4dab96b748ed040e9b308161279820447ef4553ba6e6d20ecb9962"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {ambee}
- {ambee AQAAABAAA b91280c63e1571ad928d52947cc31a14ad1bf5a83088d0346b94f6683cf22138}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"b91280c63e1571ad928d52947cc31a14ad1bf5a83088d0346b94f6683cf22138"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- [INFO] Fetching data from ambee
- [INFO] Sending request to the API
- [DEBUG] Using Key=eccb41cc2d4dab96b748ed040e9b308161279820447ef4553ba6e6d20ecb9962
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the ambee API
- [DEBUG] Using Key=eccb41cc2d4dab96y748ed040e9b308161279820447ef4553ba6e6d20ecb9962
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/amplitudeapikey/amplitudeapikey.go b/pkg/detectors/amplitudeapikey/amplitudeapikey.go
deleted file mode 100644
index d1b13a10e920..000000000000
--- a/pkg/detectors/amplitudeapikey/amplitudeapikey.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package amplitudeapikey
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"amplitude"}) + `\b([0-9a-f]{32})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"amplitude"}) + `\b([0-9a-f]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"amplitude"}
-}
-
-// FromData will find and optionally verify AmplitudeApiKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys, uniqueSecrets = make(map[string]struct{}), make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for _, matches := range secretPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueSecrets[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- for secret := range uniqueSecrets {
- // regex for both key and secret are same so the set of strings could possibly be same as well
- if key == secret {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AmplitudeApiKey,
- Raw: []byte(key),
- RawV2: []byte(key + secret),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAdobeIOSecret(ctx, client, key, secret)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyAdobeIOSecret(ctx context.Context, client *http.Client, key string, secret string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://amplitude.com/api/2/taxonomy/category", nil)
- if err != nil {
- return false, err
- }
- req.SetBasicAuth(key, secret)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AmplitudeApiKey
-}
-
-func (s Scanner) Description() string {
- return "Amplitude is a product analytics service that helps companies track and analyze user behavior within web and mobile applications. Amplitude API keys can be used to access and modify this data."
-}
diff --git a/pkg/detectors/amplitudeapikey/amplitudeapikey_integration_test.go b/pkg/detectors/amplitudeapikey/amplitudeapikey_integration_test.go
deleted file mode 100644
index 4b04165c6b44..000000000000
--- a/pkg/detectors/amplitudeapikey/amplitudeapikey_integration_test.go
+++ /dev/null
@@ -1,136 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package amplitudeapikey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAmplitudeApiKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- key := testSecrets.MustGetField("AMPLITUDEAPI_KEY")
- secret := testSecrets.MustGetField("AMPLITUDEAPI_SECRET")
- inactiveKey := testSecrets.MustGetField("AMPLITUDEAPI_KEY_INACTIVE")
- inactiveSecret := testSecrets.MustGetField("AMPLITUDEAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find an amplitude key %s with amplitude secret %s within", key, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AmplitudeApiKey,
- Verified: true,
- },
- {
- DetectorType: detectorspb.DetectorType_AmplitudeApiKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
-
- data: []byte(fmt.Sprintf("You can find an amplitude key %s with amplitude secret %s within but not valid", inactiveKey, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AmplitudeApiKey,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_AmplitudeApiKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AmplitudeApiKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no rawv2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AmplitudeApiKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/amplitudeapikey/amplitudeapikey_test.go b/pkg/detectors/amplitudeapikey/amplitudeapikey_test.go
deleted file mode 100644
index e2060fe6e0a2..000000000000
--- a/pkg/detectors/amplitudeapikey/amplitudeapikey_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package amplitudeapikey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAmplitudeAPIKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using amplitude Key=c2167730016af34b89e200ecf55710e8
- [DEBUG] Using amplitude Secret=5488620aa9073c09f1a16e2b1dc357b6
- [INFO] Response received: 200 OK
- `,
- want: []string{
- "c2167730016af34b89e200ecf55710e85488620aa9073c09f1a16e2b1dc357b6",
- "5488620aa9073c09f1a16e2b1dc357b6c2167730016af34b89e200ecf55710e8",
- },
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {amplitude aac639f65d80ec2eec96e775f598ce13}
- {amplitude AQAAABAAA 8ac62041353622f9c5e4657807ff1eac}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{
- "aac639f65d80ec2eec96e775f598ce138ac62041353622f9c5e4657807ff1eac",
- "8ac62041353622f9c5e4657807ff1eacaac639f65d80ec2eec96e775f598ce13",
- },
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using amplitude Key=c2167730016rf34b89e200ecf55710e8
- [DEBUG] Using amplitude Secret=5488620aa9073q09f1a16e2b1dc357b6
- [INFO] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/anthropic/anthropic.go b/pkg/detectors/anthropic/anthropic.go
deleted file mode 100644
index 47f07931a901..000000000000
--- a/pkg/detectors/anthropic/anthropic.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package anthropic
-
-import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(sk-ant-(?:admin01|api03)-[\w\-]{93}AA)\b`)
-
- // verification endpoints
- apiKeyEndpoint = "https://api.anthropic.com/v1/models"
- adminKeyEndpoint = "https://api.anthropic.com/v1/organizations/api_keys"
-)
-
-// 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{"sk-ant-api03", "sk-ant-admin01"}
-}
-
-// FromData will find and optionally verify Anthropic secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keys := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, key := range keys {
- keyMatch := strings.TrimSpace(key[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Anthropic,
- Raw: []byte(keyMatch),
- ExtraData: make(map[string]string),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isAdminKey := isAdminKey(keyMatch)
- var isVerified bool
- var err error
-
- if isAdminKey {
- isVerified, err = verifyAnthropicKey(ctx, client, adminKeyEndpoint, keyMatch)
- s1.ExtraData["Type"] = "Admin Key"
- } else if !isAdminKey {
- isVerified, err = verifyAnthropicKey(ctx, client, apiKeyEndpoint, keyMatch)
- s1.ExtraData["Type"] = "API Key"
- } else {
- return nil, errors.New("unknown key type detected for anthropic")
- }
-
- s1.Verified = isVerified
- s1.SetVerificationError(err, keyMatch)
-
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{
- "key": keyMatch,
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-/*
-verifyAnthropicKey verify the anthropic key passed against the endpoint
-
-Endpoints:
-
- - For api keys: https://docs.anthropic.com/en/api/models-list
-
- - For admin keys: https://docs.anthropic.com/en/api/admin-api/apikeys/list-api-keys
-*/
-func verifyAnthropicKey(ctx context.Context, client *http.Client, endpoint, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody)
- if err != nil {
- return false, nil
- }
-
- req.Header.Set("x-api-key", key)
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("anthropic-version", "2023-06-01")
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
-
- case http.StatusNotFound, http.StatusUnauthorized:
- // 404 is returned if api key is disabled or not found
- return false, nil
-
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Anthropic
-}
-
-func (s Scanner) Description() string {
- return "Anthropic is an AI research company. The API keys can be used to access their AI models and services."
-}
-
-func isAdminKey(key string) bool {
- return strings.HasPrefix(key, "sk-ant-admin01")
-}
diff --git a/pkg/detectors/anthropic/anthropic_integration_test.go b/pkg/detectors/anthropic/anthropic_integration_test.go
deleted file mode 100644
index fd7648aeae53..000000000000
--- a/pkg/detectors/anthropic/anthropic_integration_test.go
+++ /dev/null
@@ -1,146 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package anthropic
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAnthropic_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- apiKey := testSecrets.MustGetField("ANTHROPIC")
- inactiveSecret := testSecrets.MustGetField("ANTHROPIC_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a anthropic secret %s within", apiKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Anthropic,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a anthropic secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Anthropic,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a anthropic secret %s within", apiKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Anthropic,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Anthropic.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Anthropic.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/anthropic/anthropic_test.go b/pkg/detectors/anthropic/anthropic_test.go
deleted file mode 100644
index b4ea6730e82a..000000000000
--- a/pkg/detectors/anthropic/anthropic_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package anthropic
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAnthropic_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- System Log - Authentication Token Issued
- Date: 2025-02-04 14:32:10 UTC
- Server: api-secure-03.internal
- Service: Anthropic API Gateway
- API Key: sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA
- Admin Key: sk-ant-admin01-abc12fake-456def789ghij-klmnopqrstuvwx-3456yza789bcde-12fakehijklmnopby56aaaogaopaaaabc123xyzAA
-
- Log Entry:
- A new API and Admin key has been generated for service authentication. Please ensure that this key remains confidential and is not exposed in any public repositories or logs.
- `,
- want: []string{
- "sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA",
- "sk-ant-admin01-abc12fake-456def789ghij-klmnopqrstuvwx-3456yza789bcde-12fakehijklmnopby56aaaogaopaaaabc123xyzAA",
- },
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {anthropic}
- {AQAAABAAA sk-ant-api03-Dtjm9IZ_rYhS_ihHLZmPXhjJ6PN8UPp7vNO7qO3735RRDpf8xbWGinsch0McONXznUm-4KWoA7WU2otvvwHBR5QRjiLakAA}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"sk-ant-api03-Dtjm9IZ_rYhS_ihHLZmPXhjJ6PN8UPp7vNO7qO3735RRDpf8xbWGinsch0McONXznUm-4KWoA7WU2otvvwHBR5QRjiLakAA"},
- },
- {
- name: "invalid pattern",
- input: `
- System Log - Authentication Token Issued
- Date: 2025-02-04 14:32:10 UTC
- Server: api-secure-03.internal
- Service: Anthropic API Gateway
- API Key: sk-ant-api03-abc123xyz-456de-klMnopqrstuvwx-3456yza789bcde-1234fghijklmnopAA
-
- Log Entry:
- A new API key has been generated for service authentication. Please ensure that this key remains confidential and is not exposed in any public repositories or logs.
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/anypoint/anypoint.go b/pkg/detectors/anypoint/anypoint.go
deleted file mode 100644
index ecd228aea6b9..000000000000
--- a/pkg/detectors/anypoint/anypoint.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package anypoint
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\b`)
- orgPat = regexp.MustCompile(detectors.PrefixRegex([]string{"org"}) + `\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\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{"anypoint"}
-}
-
-// FromData will find and optionally verify Anypoint secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys, uniquePats = make(map[string]struct{}), make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for _, matches := range orgPat.FindAllStringSubmatch(dataStr, -1) {
- uniquePats[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- for org := range uniquePats {
- // regex for both key and org are same, so to avoid same string processing
- if key == org {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Anypoint,
- Raw: []byte(key),
- RawV2: []byte(key + org),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAnypointSecret(ctx, client, key, org)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyAnypointSecret(ctx context.Context, client *http.Client, key string, org string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://anypoint.mulesoft.com/apiplatform/repository/v2/organizations/%s/apis/by-name?apiName=%s", org, ""), nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Anypoint
-}
-
-func (s Scanner) Description() string {
- return "Anypoint is a unified platform that allows organizations to build and manage APIs and integrations. Anypoint credentials can be used to access and manipulate these integrations and API data."
-}
diff --git a/pkg/detectors/anypoint/anypoint_integration_test.go b/pkg/detectors/anypoint/anypoint_integration_test.go
deleted file mode 100644
index e073314369c3..000000000000
--- a/pkg/detectors/anypoint/anypoint_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package anypoint
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAnypoint_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ANYPOINT_TOKEN")
- inactiveSecret := testSecrets.MustGetField("ANYPOINT_INACTIVE")
- organizationId := testSecrets.MustGetField("ANYPOINT_ORGANIZATIONID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find an anypoint secret %s within organization %s", secret, organizationId)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Anypoint,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find an anypoint secret %s within organization %s but not valid", inactiveSecret, organizationId)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Anypoint,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Anypoint.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Anypoint.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/anypoint/anypoint_test.go b/pkg/detectors/anypoint/anypoint_test.go
deleted file mode 100644
index 95ecd6be1098..000000000000
--- a/pkg/detectors/anypoint/anypoint_test.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package anypoint
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAnypoint_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- # Anypoint Secret Configuration File
- # Organization details
- ORG_NAME=my_organization
- ORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr
-
- # OAuth tokens
- ACCESS_TOKEN=abcxyz123
- REFRESH_TOKEN=zyxwvutsrqponmlkji9876543210abcd
-
- # API keys
- SECRET_KEY=1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6
-
- # Endpoints
- SERVICE_URL=https://api.example.com/v1/resource
- `,
- want: []string{"1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6abcd1234-ef56-gh78-ij90-klmn1234opqr"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {anypoint org rdogw4dd-6x3l-2nm3-jvl5-qi8dyheccgj7}
- {AQAAABAAA 7jhlugw8-3tfb-7ju2-0i0y-7un6qxvknbvz}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"7jhlugw8-3tfb-7ju2-0i0y-7un6qxvknbvzrdogw4dd-6x3l-2nm3-jvl5-qi8dyheccgj7"},
- },
- {
- name: "invalid pattern",
- input: `
- # Anypoint Secret Configuration File
- # Organization details
- ORG_NAME=my_organization
- ORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr
-
- # OAuth tokens
- ACCESS_TOKEN=abcxyz123
- REFRESH_TOKEN=zyxwvutsrqponmlkji9876543210abcd
-
- # API keys
- SECRET_KEY=1a2b3C4d-5E6f-7g8H-9i0J-k1l2M3n4o5p6
-
- # Endpoints
- SERVICE_URL=https://api.example.com/v1/resource
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/anypointoauth2/anypointoauth2.go b/pkg/detectors/anypointoauth2/anypointoauth2.go
deleted file mode 100644
index d7dcbba80324..000000000000
--- a/pkg/detectors/anypointoauth2/anypointoauth2.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package anypointoauth2
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"anypoint", "id"}) + `\b([0-9a-f]{32})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"anypoint", "secret"}) + `\b([0-9a-fA-F]{32})\b`)
-
- verificationUrl = "https://anypoint.mulesoft.com/accounts/oauth2/token"
-)
-
-// 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{"anypoint"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Anypoint secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueIDs, uniqueSecrets = make(map[string]struct{}), make(map[string]struct{})
-
- for _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueIDs[matches[1]] = struct{}{}
- }
-
- for _, matches := range secretPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueSecrets[matches[1]] = struct{}{}
- }
-
- for id := range uniqueIDs {
- for secret := range uniqueSecrets {
- if id == secret {
- // Avoid processing the same string for both id and secret.
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AnypointOAuth2,
- Raw: []byte(secret),
- RawV2: []byte(fmt.Sprintf("%s:%s", id, secret)),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyMatch(ctx, client, id, secret)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
-
- }
-
- results = append(results, s1)
-
- if s1.Verified {
- // Anypoint client IDs and secrets are mapped one-to-one, so if a pair
- // is verified, we can remove that secret from the uniqueSecrets map.
- delete(uniqueSecrets, secret)
- break
- }
- }
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, id, secret string) (bool, error) {
- payload := strings.NewReader(`{"grant_type":"client_credentials","client_id":"` + id + `","client_secret":"` + secret + `"}`)
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, verificationUrl, payload)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- // The endpoint responds with status 200 for valid Organization credentials and 422 for Client credentials.
- case http.StatusOK, http.StatusUnprocessableEntity:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AnypointOAuth2
-}
-
-func (s Scanner) Description() string {
- return "Anypoint is a unified platform that allows organizations to build and manage APIs and integrations. Anypoint credentials can be used to access and manipulate these integrations and API data."
-}
diff --git a/pkg/detectors/anypointoauth2/anypointoauth2_integration_test.go b/pkg/detectors/anypointoauth2/anypointoauth2_integration_test.go
deleted file mode 100644
index e4b416bc2db9..000000000000
--- a/pkg/detectors/anypointoauth2/anypointoauth2_integration_test.go
+++ /dev/null
@@ -1,133 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package anypointoauth2
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAnypointOAuth2_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- clientID := testSecrets.MustGetField("ANYPOINT_CLIENT_ID")
- clientSecret := testSecrets.MustGetField("ANYPOINT_CLIENT_SECRET")
- inactiveSecret := testSecrets.MustGetField("ANYPOINT_INACTIVE_SECRET")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find an anypoint secret %s within anypoint organization id %s", clientSecret, clientID)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AnypointOAuth2,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find an anypoint secret %s within anypoint organization id %s but not valid", inactiveSecret, clientID)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AnypointOAuth2,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Anypoint.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", "primarySecret")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AnypointOAuth2.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/anypointoauth2/anypointoauth2_test.go b/pkg/detectors/anypointoauth2/anypointoauth2_test.go
deleted file mode 100644
index 1f2c4d4338ef..000000000000
--- a/pkg/detectors/anypointoauth2/anypointoauth2_test.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package anypointoauth2
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAnypoint_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- # Anypoint Secret Configuration File
- # Organization details
- ORG_NAME=my_organization
- ORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr
-
- # OAuth tokens
- CLIENT_ID=e3cd10a87f53b2dfa4b5fd606e7d9eca
- CLIENT_SECRET=ACE9d7E606Df5B4AFD2B35f78A01DC3E
-
- # API keys
- SECRET_KEY=1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6
-
- # Endpoints
- SERVICE_URL=https://api.example.com/v1/resource
- `,
- want: []string{"e3cd10a87f53b2dfa4b5fd606e7d9eca:ACE9d7E606Df5B4AFD2B35f78A01DC3E"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {anypoint id 17c55fba5c93de5646b10507c36fbc23}
- {AQAAABAAA 8E6Ef8F8d5De05d8BF1491e1ecC37b31}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"17c55fba5c93de5646b10507c36fbc23:8E6Ef8F8d5De05d8BF1491e1ecC37b31"},
- },
- {
- name: "invalid pattern",
- input: `
- # Anypoint Secret Configuration File
- # Organization details
- ORG_NAME=my_organization
- ORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr
-
- # OAuth tokens
- CLIENT_ID=k4lzc5ty98tnfu3a11y8gnv5vb1281as
- CLIENT_SECRET=ACE9d7E606Df5B4AFD2B35f78A01DC3E
-
- # API keys
- SECRET_KEY=1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6
-
- # Endpoints
- SERVICE_URL=https://api.example.com/v1/resource
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/apacta/apacta.go b/pkg/detectors/apacta/apacta.go
deleted file mode 100644
index 77d9a6c6c99a..000000000000
--- a/pkg/detectors/apacta/apacta.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package apacta
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apacta"}) + `\b([a-z0-9-]{36})\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{"apacta"}
-}
-
-// FromData will find and optionally verify Apacta secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Apacta,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyApactaKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyApactaKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://app.apacta.com/api/v1/time_entries?api_key=%s", key), nil)
- if err != nil {
- return false, err
- }
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Apacta
-}
-
-func (s Scanner) Description() string {
- return "Apacta is a project management tool designed for the construction industry. Apacta API keys can be used to access and manage project data within the Apacta platform."
-}
diff --git a/pkg/detectors/apacta/apacta_integration_test.go b/pkg/detectors/apacta/apacta_integration_test.go
deleted file mode 100644
index d80e7f04b6a2..000000000000
--- a/pkg/detectors/apacta/apacta_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package apacta
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestApacta_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APACTA")
- inactiveSecret := testSecrets.MustGetField("APACTA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apacta secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apacta,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apacta secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apacta,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Apacta.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Apacta.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/apacta/apacta_test.go b/pkg/detectors/apacta/apacta_test.go
deleted file mode 100644
index b03975ac3e0d..000000000000
--- a/pkg/detectors/apacta/apacta_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-package apacta
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApacta_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- // Create a new request with the secret as a header
- req, err := http.NewRequest("POST", "https://api.example.com/v1/resource", bytes.NewBuffer([]byte("{}")))
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- apactaSecret := "Bearer abcd1234-ef56-gh78-ij90-klmn1234opqr"
- req.Header.Set("Authorization", apactaSecret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"abcd1234-ef56-gh78-ij90-klmn1234opqr"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {apacta}
- {AQAAABAAA w8-p59rc70q0unyupknadu5sr8bf5us04mpt}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"w8-p59rc70q0unyupknadu5sr8bf5us04mpt"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- // Create a new request with the secret as a header
- req, err := http.NewRequest("POST", "https://api.example.com/v1/resource", bytes.NewBuffer([]byte("{}")))
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- apactaSecret := "Bearer abcD$1234-ef56-gH78-ij90-klmn1234opqr"
- req.Header.Set("Authorization", apactaSecret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/api2cart/api2cart.go b/pkg/detectors/api2cart/api2cart.go
deleted file mode 100644
index 6fa8c2bda26c..000000000000
--- a/pkg/detectors/api2cart/api2cart.go
+++ /dev/null
@@ -1,118 +0,0 @@
-package api2cart
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"api2cart"}) + `\b([0-9a-f]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"api2cart"}
-}
-
-// FromData will find and optionally verify Api2Cart secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Api2Cart,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyApi2CartKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyApi2CartKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://api.api2cart.com/v1.1/account.cart.list.json?api_key=%s", key), nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Accept", "application/json")
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- body, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
-
- var result Response
- if err := json.Unmarshal(body, &result); err != nil {
- return false, err
- }
- if result.ReturnCode == 0 {
- return true, nil
- }
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
- return false, nil
-}
-
-type Response struct {
- ReturnCode int `json:"return_code"`
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Api2Cart
-}
-
-func (s Scanner) Description() string {
- return "Api2Cart is a unified shopping cart data interface that allows interaction with multiple shopping cart platforms. Api2Cart API keys can be used to access and manipulate shopping cart data."
-}
diff --git a/pkg/detectors/api2cart/api2cart_integration_test.go b/pkg/detectors/api2cart/api2cart_integration_test.go
deleted file mode 100644
index f0243d52bcc9..000000000000
--- a/pkg/detectors/api2cart/api2cart_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package api2cart
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestApi2Cart_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("API2CART")
- inactiveSecret := testSecrets.MustGetField("API2CART_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a api2cart secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Api2Cart,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a api2cart secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Api2Cart,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Api2Cart.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Api2Cart.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/api2cart/api2cart_test.go b/pkg/detectors/api2cart/api2cart_test.go
deleted file mode 100644
index e31fdb1ad7fd..000000000000
--- a/pkg/detectors/api2cart/api2cart_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package api2cart
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApi2Cart_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- To integrate with API2Cart, ensure you have the following credentials in your configuration file.
- Your API2CART key is 2afddb813193eb9d3b5bd99bf5d834cd, which you will need to access the API securely.
-
- The following endpoints are available for your use:
- - Get Products: https://api.api2cart.com/v1.0/products/get
- - Add Product: https://api.api2cart.com/v1.0/products/add
- `,
- want: []string{"2afddb813193eb9d3b5bd99bf5d834cd"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {api2cart}
- {AQAAABAAA b36c17e9dc0dba67480e864cf69879c3}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"b36c17e9dc0dba67480e864cf69879c3"},
- },
- {
- name: "invalid pattern",
- input: `
- To integrate with API2Cart, ensure you have the following credentials in your configuration file.
- Your API2CART key is 68d746609J4240840734c22836725d76, which you will need to access the API securely.
-
- The following endpoints are available for your use:
- - Get Products: https://api.api2cart.com/v1.0/products/get
- - Add Product: https://api.api2cart.com/v1.0/products/add
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/apideck/apideck.go b/pkg/detectors/apideck/apideck.go
deleted file mode 100644
index 5b109614be14..000000000000
--- a/pkg/detectors/apideck/apideck.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package apideck
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(sk_live_[a-z0-9A-Z-]{93})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apideck"}) + `\b([a-z0-9A-Z]{40})\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{"apideck"}
-}
-
-// FromData will find and optionally verify ApiDeck secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys, uniqueIds = make(map[string]struct{}), make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueIds[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- for id := range uniqueIds {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ApiDeck,
- Raw: []byte(key),
- RawV2: []byte(key + id),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAdobeIOSecret(ctx, client, key, id)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func verifyAdobeIOSecret(ctx context.Context, client *http.Client, key string, id string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://unify.apideck.com/vault/consumers", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("x-apideck-app-id", id)
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ApiDeck
-}
-
-func (s Scanner) Description() string {
- return "ApiDeck is a platform that provides a unified API to connect multiple services. ApiDeck keys can be used to access and manage these services."
-}
diff --git a/pkg/detectors/apideck/apideck_integration_test.go b/pkg/detectors/apideck/apideck_integration_test.go
deleted file mode 100644
index 2a16e8a2a05e..000000000000
--- a/pkg/detectors/apideck/apideck_integration_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package apideck
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestApiDeck_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APIDECK_TOKEN")
- inactiveSecret := testSecrets.MustGetField("APIDECK_INACTIVE")
- id := testSecrets.MustGetField("APIDECK_APPID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apideck secret %s within apideckid %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ApiDeck,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apideck secret %s within apideckid %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ApiDeck,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ApiDeck.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no rawv2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ApiDeck.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/apideck/apideck_test.go b/pkg/detectors/apideck/apideck_test.go
deleted file mode 100644
index dc8f050742bf..000000000000
--- a/pkg/detectors/apideck/apideck_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package apideck
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApiDeck_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the apideck API
- [DEBUG] Using Key=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
- [DEBUG] Using apideck ID=VfKE9Zh2ZatnqmrloqDu3PCnkNBR6Io4TlSbsG1P
- [INFO] Response received: 200 OK
- `,
- want: []string{
- "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",
- },
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {apideck id J6rYP2lzThxp9JeGg74TDgAXvfQsvzonsHpYHDsG}
- {apideck AQAAABAAA sk_live_R5S2B88smT6QfTsUc3o3DedI2hbbcnZwvQKjyudQ41V0T38L8qUDPUTlBDcVE2NwRp1PowPYqnmAHlZ-W1Yr7AWGvpCvT}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"sk_live_R5S2B88smT6QfTsUc3o3DedI2hbbcnZwvQKjyudQ41V0T38L8qUDPUTlBDcVE2NwRp1PowPYqnmAHlZ-W1Yr7AWGvpCvTJ6rYP2lzThxp9JeGg74TDgAXvfQsvzonsHpYHDsG"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the apideck API
- [DEBUG] Using Key=sk_live_GKE08ADdkDV1DQ4vDfaW4ejDHy-TkotfxDmHvQMLX0HRvhtfPwku6olGvsG2vXBg869A0hsOPHHOw48SAF2GO7jBMs6Rt
- [DEBUG] Using apideck ID=VfKE9Zh2ZatnqmrloqDu3PC_kNBR6Io4TlSbsG1P
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/apiflash/apiflash.go b/pkg/detectors/apiflash/apiflash.go
deleted file mode 100644
index d4ef185d213a..000000000000
--- a/pkg/detectors/apiflash/apiflash.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package apiflash
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apiflash"}) + `\b([a-z0-9]{32})\b`)
-
- urlToCapture = "http://google.com" // a fix constant url to capture to verify the access key
-)
-
-// 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{"apiflash"}
-}
-
-// FromData will find and optionally verify Apiflash secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueAPIKeys := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueAPIKeys[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for key := range uniqueAPIKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Apiflash,
- Raw: []byte(key),
- }
-
- if verify {
- isVerified, verificationErr := verifyAPIFlash(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, key)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Apiflash
-}
-
-func (s Scanner) Description() string {
- return "Apiflash is a screenshot API service. Apiflash keys can be used to access and utilize the screenshot API service."
-}
-
-func verifyAPIFlash(ctx context.Context, client *http.Client, accessKey string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.apiflash.com/v1/urltoimage?url=%s&access_key=%s&wait_until=page_loaded", urlToCapture, accessKey), http.NoBody)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, nil
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/apiflash/apiflash_integration_test.go b/pkg/detectors/apiflash/apiflash_integration_test.go
deleted file mode 100644
index c457a47225f2..000000000000
--- a/pkg/detectors/apiflash/apiflash_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package apiflash
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestApiflash_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APIFLASH")
- url := testSecrets.MustGetField("API_URL")
- inactiveSecret := testSecrets.MustGetField("APIFLASH_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apiflash secret %s within apiflash %s", secret, url)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apiflash,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apiflash secret %s within apiflash %s but not valid", inactiveSecret, url)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apiflash,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Apiflash.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Apiflash.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/apiflash/apiflash_test.go b/pkg/detectors/apiflash/apiflash_test.go
deleted file mode 100644
index a90840fb25a0..000000000000
--- a/pkg/detectors/apiflash/apiflash_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package apiflash
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApiFlash_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the apiflash API
- [DEBUG] Using Key=grevetn5owrs1ybhxtcen0ibvg2mi85x
- [INFO] Response received: 200 OK
- `,
- want: []string{"grevetn5owrs1ybhxtcen0ibvg2mi85x"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {apiflash}
- {AQAAABAAA axlzvcf9m7jyyts833f9gmtcpqe5b26o}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"axlzvcf9m7jyyts833f9gmtcpqe5b26o"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the apiflash API
- [DEBUG] Using Key=grevetn5owRs1ybhxtcen0ibvg2mi85x
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/apifonica/apifonica.go b/pkg/detectors/apifonica/apifonica.go
deleted file mode 100644
index 3c6b17b91e43..000000000000
--- a/pkg/detectors/apifonica/apifonica.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package apifonica
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apifonica"}) + `\b([0-9a-z]{11}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\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{"apifonica"}
-}
-
-// FromData will find and optionally verify Apifonica secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys, uniqueTokens = make(map[string]struct{}), make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokens[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- for token := range uniqueTokens {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ApiFonica,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyApifonicaSecret(ctx, client, key, token)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyApifonicaSecret(ctx context.Context, client *http.Client, key string, token string) (bool, error) {
- data := fmt.Sprintf("%s:%s", key, token)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.apifonica.com/v2/accounts", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ApiFonica
-}
-
-func (s Scanner) Description() string {
- return "Apifonica is a cloud communication platform that provides APIs for messaging, voice, and other communication services. Apifonica keys can be used to access and manage these services."
-}
diff --git a/pkg/detectors/apifonica/apifonica_integration_test.go b/pkg/detectors/apifonica/apifonica_integration_test.go
deleted file mode 100644
index 8df2c4cb50a8..000000000000
--- a/pkg/detectors/apifonica/apifonica_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package apifonica
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestApifonica_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APIFONICA_SECRET")
- token := testSecrets.MustGetField("APIFONICA_ID")
- inactiveSecret := testSecrets.MustGetField("APIFONICA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apifonica secret %s within apifonica token %s", secret, token)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ApiFonica,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apifonica secret %s within apifonica token %s but not valid", inactiveSecret, token)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ApiFonica,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Apifonica.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Apifonica.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/apifonica/apifonica_test.go b/pkg/detectors/apifonica/apifonica_test.go
deleted file mode 100644
index cd0f91c41b40..000000000000
--- a/pkg/detectors/apifonica/apifonica_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package apifonica
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApiFonica_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the apifonica API
- [DEBUG] Using Key=4rv0hdx5188-3q48-2luk-e8v5-dyuuf8l44ib7
- [INFO] Response received: 200 OK
- `,
- want: []string{"4rv0hdx5188-3q48-2luk-e8v5-dyuuf8l44ib7"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {apifonica}
- {AQAAABAAA fvzlzj17xzz-lwon-842u-46bs-5spcl2g7u9eb}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"fvzlzj17xzz-lwon-842u-46bs-5spcl2g7u9eb"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the apifonica API
- [DEBUG] Using Key=4rv0hdx51889-3q48-2luk-e8wv5-dyuuf8l44ib7
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/apify/apify.go b/pkg/detectors/apify/apify.go
deleted file mode 100644
index 682d0f9d7871..000000000000
--- a/pkg/detectors/apify/apify.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package apify
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(apify\_api\_[a-zA-Z-0-9]{36})\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{"apify"}
-}
-
-// FromData will find and optionally verify Apify secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Apify,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyApifyKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyApifyKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.apify.com/v2/acts?token="+key+"&my=true&offset=10&limit=99&desc=true", nil)
- if err != nil {
- return false, err
- }
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Apify
-}
-
-func (s Scanner) Description() string {
- return "Apify is a platform for web scraping and automation. Apify API keys can be used to access and control Apify actors and tasks."
-}
diff --git a/pkg/detectors/apify/apify_integration_test.go b/pkg/detectors/apify/apify_integration_test.go
deleted file mode 100644
index 79e866953358..000000000000
--- a/pkg/detectors/apify/apify_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package apify
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestApify_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APIFY")
- inactiveSecret := testSecrets.MustGetField("APIFY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apify secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apify,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apify secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apify,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Apify.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Apify.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/apify/apify_test.go b/pkg/detectors/apify/apify_test.go
deleted file mode 100644
index 3e533031c15c..000000000000
--- a/pkg/detectors/apify/apify_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package apify
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApiFy_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using Key=apify_api_dXB1vLsglgTexUYm3JTAx2BHTjVuDBbvPl8R
- [INFO] Response received: 200 OK
- `,
- want: []string{"apify_api_dXB1vLsglgTexUYm3JTAx2BHTjVuDBbvPl8R"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {user-id}
- {AQAAABAAA apify_api_RpTLEX9U18xfGl90wDaT2V9R-YX0TlMpxIzi}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"apify_api_RpTLEX9U18xfGl90wDaT2V9R-YX0TlMpxIzi"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using Key=apify_api_dXB1vLPglgTex_UYm3JTAx2BHTjVuDBbvPl8R
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/apilayer/apilayer.go b/pkg/detectors/apilayer/apilayer.go
deleted file mode 100644
index f3b50b620b34..000000000000
--- a/pkg/detectors/apilayer/apilayer.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package apilayer
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apilayer"}) + `\b([a-zA-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"apilayer"}
-}
-
-// FromData will find and optionally verify Apilayer secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Apilayer,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAPILayerKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAPILayerKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.apilayer.com/number_verification/countries", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("apikey", key)
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Apilayer
-}
-
-func (s Scanner) Description() string {
- return "Apilayer is a service providing various APIs for data verification and other utilities. Apilayer API keys can be used to access these services and perform operations such as number verification."
-}
diff --git a/pkg/detectors/apilayer/apilayer_integration_test.go b/pkg/detectors/apilayer/apilayer_integration_test.go
deleted file mode 100644
index ef4b0fe2f0ab..000000000000
--- a/pkg/detectors/apilayer/apilayer_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package apilayer
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestApilayer_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APILAYER")
- inactiveSecret := testSecrets.MustGetField("APILAYER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apilayer secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apilayer,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apilayer secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apilayer,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Apilayer.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Apilayer.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/apilayer/apilayer_test.go b/pkg/detectors/apilayer/apilayer_test.go
deleted file mode 100644
index 3e9cef682279..000000000000
--- a/pkg/detectors/apilayer/apilayer_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package apilayer
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApiLayer_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the apilayer API
- [DEBUG] Using Key=qnHT110fihCn49wOm5b2h3ACTRmksbg0
- [INFO] Response received: 200 OK
- `,
- want: []string{"qnHT110fihCn49wOm5b2h3ACTRmksbg0"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {apilayer}
- {AQAAABAAA HHTi3DYZIqt57j5WVHvXHHboXpCnm6CW}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"HHTi3DYZIqt57j5WVHvXHHboXpCnm6CW"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the apilayer API
- [DEBUG] Using Key=qnHT110fiha-Cn49wOm5b2h3ACTRmksbg0
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/apimatic/apimatic.go b/pkg/detectors/apimatic/apimatic.go
deleted file mode 100644
index 754c109c3716..000000000000
--- a/pkg/detectors/apimatic/apimatic.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package apimatic
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- apiKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apimatic", "apikey"}) + `\b([a-zA-Z0-9_-]{64})\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{"apimatic"}
-}
-
-// FromData will find and optionally verify APIMatic secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueApiKeys := make(map[string]struct{})
- for _, matches := range apiKeyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueApiKeys[matches[1]] = struct{}{}
- }
-
- for apiKey := range uniqueApiKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_APIMatic,
- Raw: []byte(apiKey),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAPImaticKey(ctx, client, apiKey)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
-
- }
- return results, nil
-}
-
-func verifyAPImaticKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- timeout := 10 * time.Second
- client.Timeout = timeout
-
- // api docs: https://docs.apimatic.io/platform-api/#/http/api-endpoints/code-generation-external-apis/list-all-code-generations
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.apimatic.io/code-generations", http.NoBody)
- if err != nil {
- return false, err
- }
-
- // authentication documentation: https://docs.apimatic.io/platform-api/#/http/guides/authentication
- req.Header.Set("Authorization", "X-Auth-Key "+key)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_APIMatic
-}
-
-func (s Scanner) Description() string {
- return "APIMatic provides tools for generating SDKs, API documentation, and code snippets. APIMatic credentials can be used to access and manage these tools and services."
-}
diff --git a/pkg/detectors/apimatic/apimatic_integration_test.go b/pkg/detectors/apimatic/apimatic_integration_test.go
deleted file mode 100644
index 7995bfc48a67..000000000000
--- a/pkg/detectors/apimatic/apimatic_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package apimatic
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAPIMatic_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APIMATIC")
- pass := testSecrets.MustGetField("APIMATIC_PASS")
- inactiveSecret := testSecrets.MustGetField("APIMATIC_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apimatic secret %s within apimatic pass %s", secret, pass)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_APIMatic,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apimatic secret %s within apimatic pass %s but not valid", inactiveSecret, pass)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_APIMatic,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("APIMatic.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("APIMatic.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/apimatic/apimatic_test.go b/pkg/detectors/apimatic/apimatic_test.go
deleted file mode 100644
index 368a481b027d..000000000000
--- a/pkg/detectors/apimatic/apimatic_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package apimatic
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApiMatic_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func validateApiMatic() bool {
- apiMaticKey := "rc6iLoUEFGGAWNLsuBJnmsh4tZB-oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB"
-
- // isActive check if the key is active or not
- return isActive(apiMaticKey)
- }`,
- want: []string{"rc6iLoUEFGGAWNLsuBJnmsh4tZB-oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {apimatic}
- {AQAAABAAA 2eqQBh9HkE-5Mq5Ma_vOEvvyt-x9shcZ-T5B7hSY1C5xvTl7qLMwGL6QAoNYmMcF}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"2eqQBh9HkE-5Mq5Ma_vOEvvyt-x9shcZ-T5B7hSY1C5xvTl7qLMwGL6QAoNYmMcF"},
- },
- {
- name: "invalid pattern",
- input: `
- func validateApiMatic() bool {
- apiMaticKey := "rc6iLoUEFGGAWNLsuBJnmsh4tZB@oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB"
-
- // isActive check if the key is active or not
- return isActive(apiMaticKey)
- }`,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/apimetrics/apimetrics.go b/pkg/detectors/apimetrics/apimetrics.go
deleted file mode 100644
index cf477535ea70..000000000000
--- a/pkg/detectors/apimetrics/apimetrics.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package apimetrics
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apimetrics"}) + `\b([a-zA-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"apimetrics"}
-}
-
-// FromData will find and optionally verify ApiMetrics secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ApiMetrics,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAPIMetricsKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAPIMetricsKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://client.apimetrics.io/api/2/calls/", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ApiMetrics
-}
-
-func (s Scanner) Description() string {
- return "ApiMetrics is a tool for monitoring the performance of APIs. ApiMetrics keys can be used to access and manage API monitors."
-}
diff --git a/pkg/detectors/apimetrics/apimetrics_integration_test.go b/pkg/detectors/apimetrics/apimetrics_integration_test.go
deleted file mode 100644
index a16dc409337a..000000000000
--- a/pkg/detectors/apimetrics/apimetrics_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package apimetrics
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestApiMetrics_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APIMETRICS")
- inactiveSecret := testSecrets.MustGetField("APIMETRICS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apimetrics secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ApiMetrics,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apimetrics secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ApiMetrics,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ApiMetrics.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ApiMetrics.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/apimetrics/apimetrics_test.go b/pkg/detectors/apimetrics/apimetrics_test.go
deleted file mode 100644
index b23c6de10659..000000000000
--- a/pkg/detectors/apimetrics/apimetrics_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package apimetrics
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApiMetrics_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func validateApiMetrics() bool {
- apiMetrics := "5po8TFGawiYNCc1ct4ofWkBqzIfA6IeO"
-
- // isActive check if the key is active or not
- return isActive(apiMetrics)
- }`,
- want: []string{"5po8TFGawiYNCc1ct4ofWkBqzIfA6IeO"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {apimetrics}
- {AQAAABAAA XpLTBFZccOgbbtVht4OaZzsrgKdh42RX}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"XpLTBFZccOgbbtVht4OaZzsrgKdh42RX"},
- },
- {
- name: "invalid pattern",
- input: `
- func validateApiMetrics() bool {
- apiMetrics := "5po8TFGawiYNCc1c4ofWkBqzIfA6IeO"
-
- // isActive check if the key is active or not
- return isActive(apiMetrics)
- }`,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/apitemplate/apitemplate.go b/pkg/detectors/apitemplate/apitemplate.go
deleted file mode 100644
index 757564ef050e..000000000000
--- a/pkg/detectors/apitemplate/apitemplate.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package apitemplate
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apitemplate"}) + `\b([0-9a-zA-Z]{39})\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{"apitemplate"}
-}
-
-// FromData will find and optionally verify APITemplate secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_APITemplate,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyAPITemplateKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyAPITemplateKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.apitemplate.io/v1/list-templates", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-API-KEY", key)
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_APITemplate
-}
-
-func (s Scanner) Description() string {
- return "APITemplate is a service used to generate documents and images from templates. APITemplate API keys can be used to access and generate these documents and images."
-}
diff --git a/pkg/detectors/apitemplate/apitemplate_integration_test.go b/pkg/detectors/apitemplate/apitemplate_integration_test.go
deleted file mode 100644
index ff20dbdbd821..000000000000
--- a/pkg/detectors/apitemplate/apitemplate_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package apitemplate
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAPITemplate_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APITEMPLATE")
- inactiveSecret := testSecrets.MustGetField("APITEMPLATE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apitemplate secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_APITemplate,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apitemplate secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_APITemplate,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("APITemplate.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("APITemplate.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/apitemplate/apitemplate_test.go b/pkg/detectors/apitemplate/apitemplate_test.go
deleted file mode 100644
index 057fb63e9b37..000000000000
--- a/pkg/detectors/apitemplate/apitemplate_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package apitemplate
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApiTemplate_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func validateKey() bool {
- apiTemplate := "EeOPHL7PyBlUk0qkJX72sDtdNL3WLdpxg1czllR"
-
- // isActive check if the key is active or not
- return isActive(apiTemplate)
- }`,
- want: []string{"EeOPHL7PyBlUk0qkJX72sDtdNL3WLdpxg1czllR"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {apitemplate}
- {AQAAABAAA oVqX8yfzlUtzudNnvlWKNI4pNKTKTwlaKmxlcX5}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"oVqX8yfzlUtzudNnvlWKNI4pNKTKTwlaKmxlcX5"},
- },
- {
- name: "invalid pattern",
- input: `
- func validateKey() bool {
- apiTemplate := "EeOPHL7PyBlUk0qkJAX72sDtdNL3WLdpxg1czllR"
-
- // isActive check if the key is active or not
- return isActive(apiTemplate)
- }`,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/apollo/apollo.go b/pkg/detectors/apollo/apollo.go
deleted file mode 100644
index 17f91e6ed053..000000000000
--- a/pkg/detectors/apollo/apollo.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package apollo
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apollo"}) + `\b([a-zA-Z0-9]{22})\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{"apollo"}
-}
-
-// FromData will find and optionally verify Apollo secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Apollo,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyApolloKey(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyApolloKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.apollo.io/api/v1/mixed_people/search", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("x-api-key", key)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Apollo
-}
-
-func (s Scanner) Description() string {
- return "Apollo is a sales intelligence platform. Apollo API keys can be used to access and modify data within the Apollo platform."
-}
diff --git a/pkg/detectors/apollo/apollo_integration_test.go b/pkg/detectors/apollo/apollo_integration_test.go
deleted file mode 100644
index 99ea2c755f97..000000000000
--- a/pkg/detectors/apollo/apollo_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package apollo
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestApollo_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APOLLO")
- inactiveSecret := testSecrets.MustGetField("APOLLO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apollo secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apollo,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apollo secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apollo,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Apollo.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Apollo.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/apollo/apollo_test.go b/pkg/detectors/apollo/apollo_test.go
deleted file mode 100644
index b7934d23038f..000000000000
--- a/pkg/detectors/apollo/apollo_test.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package apollo
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApollo_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func validateApolloKey() bool {
- apiKey := "897TJ1HevanW9Ye6nv6Ojj"
- log.Println("Checking API key status...")
-
- if !isActive(apiKey) {
- log.Println("API key is inactive or invalid.")
- return false
- }
-
- log.Println("API key is valid and active.")
- return true
- }`,
- want: []string{"897TJ1HevanW9Ye6nv6Ojj"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {apollo}
- {AQAAABAAA S2wg2NMlgalg9AUsrXPd1O}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"S2wg2NMlgalg9AUsrXPd1O"},
- },
- {
- name: "invalid pattern",
- input: `
- func validateApolloKey() bool {
- apiKey := "897TJ1HevanW9Ye-nv6Ojj"
- log.Println("Checking API key status...")
-
- if !isActive(apiKey) {
- log.Println("API key is inactive or invalid.")
- return false
- }
-
- log.Println("API key is valid and active.")
- return true
- }`,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/appcues/appcues.go b/pkg/detectors/appcues/appcues.go
deleted file mode 100644
index 5f378d95ed13..000000000000
--- a/pkg/detectors/appcues/appcues.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package appcues
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"appcues"}) + `\b([a-z0-9-]{36})\b`)
- userPat = regexp.MustCompile(detectors.PrefixRegex([]string{"appcues"}) + `\b([a-z0-9-]{39})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"appcues"}) + `\b([0-9]{5})\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{"appcues"}
-}
-
-// FromData will find and optionally verify Appcues secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- userMatches := userPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, userMatch := range userMatches {
-
- resUserMatch := strings.TrimSpace(userMatch[1])
-
- for _, idMatch := range idMatches {
-
- resIdMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Appcues,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resUserMatch),
- }
- if verify {
- isVerified, err := verifyMatch(ctx, client, resUserMatch, resMatch, resIdMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resUserMatch, resMatch, resIdMatch)
- }
-
- results = append(results, s1)
- }
- }
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, resUserMatch, resMatch, resIdMatch string) (bool, error) {
- // Reference: https://api.appcues.com/v2/docs?_gl=1#responses
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://api.appcues.com/v2/accounts/%s/flows", resIdMatch), http.NoBody)
- if err != nil {
- return false, err
- }
- req.SetBasicAuth(resUserMatch, resMatch)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Appcues
-}
-
-func (s Scanner) Description() string {
- return "Appcues is a user engagement platform that helps create personalized user experiences. The detected credentials can be used to access and manage user engagement flows and data."
-}
diff --git a/pkg/detectors/appcues/appcues_integration_test.go b/pkg/detectors/appcues/appcues_integration_test.go
deleted file mode 100644
index eb36ecac7dcb..000000000000
--- a/pkg/detectors/appcues/appcues_integration_test.go
+++ /dev/null
@@ -1,134 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package appcues
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAppcues_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APPCUES")
- user := testSecrets.MustGetField("APPCUES_USER")
- id := testSecrets.MustGetField("APPCUES_ID")
- inactiveSecret := testSecrets.MustGetField("APPCUES_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a appcues secret %s within appcues user %s and appcues id %s", secret, user, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Appcues,
- Verified: true,
- },
- {
- DetectorType: detectorspb.DetectorType_Appcues,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a appcues secret %s within appcues user %s and appcues id %s but not valid", inactiveSecret, user, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Appcues,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Appcues,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Appcues.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no rawv2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Appcues.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/appcues/appcues_test.go b/pkg/detectors/appcues/appcues_test.go
deleted file mode 100644
index a8b037d93c2b..000000000000
--- a/pkg/detectors/appcues/appcues_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package appcues
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAppCues_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the appcues API
- [DEBUG] Using appcues Key=5g5n4yazu-dpqp3g6qt3gn59wrxhqf2mqipm
- [DEBUG] Using appcues User=truffle-security-lrv10a8l4u23xp5gkvg819
- [INFO] Response received: 200 OK
- [INFO] APPCUES_ID=57843
- `,
- want: []string{"5g5n4yazu-dpqp3g6qt3gn59wrxhqf2mqipmtruffle-security-lrv10a8l4u23xp5gkvg819"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {appcues 91712}
- {appcues ubdcpht45hlfdywxv89ympnvtcnydl3uv-0umfu}
- {appcues AQAAABAAA w9hyyfghqirj8uwcmtv05-n4fppzl-in223u}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"w9hyyfghqirj8uwcmtv05-n4fppzl-in223uubdcpht45hlfdywxv89ympnvtcnydl3uv-0umfu"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the appcues API
- [DEBUG] Using appcues Key=5g5n4yazu-dpqp3g6qt3gn59wrxhqf2mqipm
- [DEBUG] Using appcues User=truffle_security-lrv10a8l4u23xp5gkvg819
- [ERROR] Response received: 401 UnAuthorized
- [INFO] ID=57843
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/appfollow/appfollow.go b/pkg/detectors/appfollow/appfollow.go
deleted file mode 100644
index c3e439adc61c..000000000000
--- a/pkg/detectors/appfollow/appfollow.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package appfollow
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"appfollow"}) + `\b(eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9\.[0-9A-Za-z]{74}\.[0-9A-Z-a-z\-_]{43})\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{"appfollow"}
-}
-
-// FromData will find and optionally verify Appfollow secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Appfollow,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- // Reference: https://docs.api.appfollow.io/reference/users_list_api_v2_account_users_get-1
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.appfollow.io/api/v2/account/users", http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("X-AppFollow-API-Token", token)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
- switch res.StatusCode {
- case http.StatusOK, http.StatusPaymentRequired, http.StatusUnprocessableEntity:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Appfollow
-}
-
-func (s Scanner) Description() string {
- return "Appfollow is a service used for app monitoring and analytics. Appfollow API tokens can be used to access and manage app data and analytics."
-}
diff --git a/pkg/detectors/appfollow/appfollow_integration_test.go b/pkg/detectors/appfollow/appfollow_integration_test.go
deleted file mode 100644
index fd6e46745cec..000000000000
--- a/pkg/detectors/appfollow/appfollow_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package appfollow
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAppfollow_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APPFOLLOW")
- inactiveSecret := testSecrets.MustGetField("APPFOLLOW_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a appfollow secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Appfollow,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a appfollow secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Appfollow,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Appfollow.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Appfollow.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/appfollow/appfollow_test.go b/pkg/detectors/appfollow/appfollow_test.go
deleted file mode 100644
index 7ccf4709b3d5..000000000000
--- a/pkg/detectors/appfollow/appfollow_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package appfollow
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAppFollow_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func validateAppFollowKey() bool {
- key := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.hdMLjiIayyb5cgbcVtjKywQwqeNKnsxZEhnJnX6wzhnblpmpjF4c2mbdmVVylTayE6M8ZE3h4V.fmnUM4cjvbe1JMFDuBSwWNEYQFHrD5AEm6p2Ir9w7K6"
-
- // isActive check if the key is active or not
- return isActive(key)
- }`,
- want: []string{"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.hdMLjiIayyb5cgbcVtjKywQwqeNKnsxZEhnJnX6wzhnblpmpjF4c2mbdmVVylTayE6M8ZE3h4V.fmnUM4cjvbe1JMFDuBSwWNEYQFHrD5AEm6p2Ir9w7K6"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {appfollow}
- {AQAAABAAA eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.YwK6gJ8sMVylaDNuXRiGFLRR1kgZaLF45EbJ0qHSRaW4CRtWaqWciTZZXxkk4a4wLh8f7cTTlb.wvTVCRC1RLCpd98q4WK3ef6M3TBrb08AkS9-jNOdA_r}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.YwK6gJ8sMVylaDNuXRiGFLRR1kgZaLF45EbJ0qHSRaW4CRtWaqWciTZZXxkk4a4wLh8f7cTTlb.wvTVCRC1RLCpd98q4WK3ef6M3TBrb08AkS9-jNOdA_r"},
- },
- {
- name: "invalid pattern",
- input: `
- func validateAppFollowKey() bool {
- apiKey := "eyJ0eXAiOiJKV1QiLCJhbGCiOiJIUzI1NiJ9.hdMLjiIayyb5cgbcVtjKywQwqeNKnsxZEhnJnX6wzhnblpmpjF4c2mbdVylTayE6M8ZE3h4V.fmnUM4cjvbe1JMFDuBSwWNEYQFHrDEm6p2Ir9w7K6"
- log.Println("Checking API key status...")
-
- if !isActive(apiKey) {
- log.Println("API key is inactive or invalid.")
- return false
- }
-
- log.Println("API key is valid and active.")
- return true
- }`,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/appointedd/appointedd.go b/pkg/detectors/appointedd/appointedd.go
deleted file mode 100644
index aed88100f046..000000000000
--- a/pkg/detectors/appointedd/appointedd.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package appointedd
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"appointedd"}) + `\b([a-zA-Z0-9=+]{88})`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"appointedd"}
-}
-
-// FromData will find and optionally verify appointedd secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Appointedd,
- Raw: []byte(resMatch),
- }
- if verify {
- isVerified, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, secret string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.appointedd.com/v1/availability/slots", http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("X-API-KEY", secret)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
- return strings.Contains(string(bodyBytes), "total"), nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Appointedd
-}
-
-func (s Scanner) Description() string {
- return "Appointedd provides online booking and scheduling services. The API key can be used to access and manage booking data."
-}
diff --git a/pkg/detectors/appointedd/appointedd_integration_test.go b/pkg/detectors/appointedd/appointedd_integration_test.go
deleted file mode 100644
index 1f2a8dfe66da..000000000000
--- a/pkg/detectors/appointedd/appointedd_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package appointedd
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAppointedd_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APPOINTEDD")
- inactiveSecret := testSecrets.MustGetField("APPOINTEDD_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a appointedd secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Appointedd,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a appointedd secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Appointedd,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Appointedd.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Appointedd.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/appointedd/appointedd_test.go b/pkg/detectors/appointedd/appointedd_test.go
deleted file mode 100644
index f554ce2e5d35..000000000000
--- a/pkg/detectors/appointedd/appointedd_test.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package appointedd
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAppFollow_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func validateAppointeddKey() bool {
- appointeddKey := "Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO=XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9=Jy6yCOXmhbpEZPfY7Y"
- log.Println("Checking API key status...")
-
- if !isActive(appointeddKey) {
- log.Println("API key is inactive or invalid.")
- return false
- }
-
- log.Println("API key is valid and active.")
- return true
- }`,
- want: []string{"Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO=XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9=Jy6yCOXmhbpEZPfY7Y"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {appointedd}
- {AQAAABAAA 2pRMKW=JrG9+xYmqlJMa4Omf9goqsSqsM3mIaqG8tG4lwnVrKIslbn=IpLIz7GTDEJUcQ0wlr6B+UjfvSY9XKXwu}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"2pRMKW=JrG9+xYmqlJMa4Omf9goqsSqsM3mIaqG8tG4lwnVrKIslbn=IpLIz7GTDEJUcQ0wlr6B+UjfvSY9XKXwu"},
- },
- {
- name: "invalid pattern",
- input: `
- func validateAppointeddKey() bool {
- appointeddKey := "Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO-XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9-Jy6yCOXmhbpEZPfY7Y"
- log.Println("Checking API key status...")
-
- if !isActive(appointeddKey) {
- log.Println("API key is inactive or invalid.")
- return false
- }
-
- log.Println("API key is valid and active.")
- return true
- }`,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/appoptics/appoptics.go b/pkg/detectors/appoptics/appoptics.go
deleted file mode 100644
index 5f91103a1be1..000000000000
--- a/pkg/detectors/appoptics/appoptics.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package appoptics
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"appoptics"}) + `\b([0-9a-zA-Z_-]{71})\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{"appoptics"}
-}
-
-// FromData will find and optionally verify Appoptics secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AppOptics,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.appoptics.com/v1/metrics", nil)
- if err != nil {
- continue
- }
-
- data := fmt.Sprintf("%s:", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
-
- req.Header.Add("Content-Type", "application/json")
- 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 if res.StatusCode == 401 {
- // The secret is determinately not verified (nothing to do)
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AppOptics
-}
-
-func (s Scanner) Description() string {
- return "AppOptics is a cloud-based infrastructure monitoring service. AppOptics API keys can be used to access and manage monitoring data and configurations."
-}
diff --git a/pkg/detectors/appoptics/appoptics_integration_test.go b/pkg/detectors/appoptics/appoptics_integration_test.go
deleted file mode 100644
index a50a583b039c..000000000000
--- a/pkg/detectors/appoptics/appoptics_integration_test.go
+++ /dev/null
@@ -1,128 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package appoptics
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAppoptics_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APPOPTICS")
- inactiveSecret := testSecrets.MustGetField("APPOPTICS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a appoptics secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AppOptics,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a appoptics secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AppOptics,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Appoptics.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Appoptics.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/appoptics/appoptics_test.go b/pkg/detectors/appoptics/appoptics_test.go
deleted file mode 100644
index a29bfd069432..000000000000
--- a/pkg/detectors/appoptics/appoptics_test.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package appoptics
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAppOptics_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func validateAppOpticsKey() bool {
- appopticsKey := "Xwl4ViaAFDLrAmFX9g1blkUVC5dJj2he3a1tzkpJ4-PznQukQruRjqMFbEG73L92LJyBGMZ"
- log.Println("Checking API key status...")
-
- if !isActive(appopticsKey) {
- log.Println("API key is inactive or invalid.")
- return false
- }
-
- log.Println("API key is valid and active.")
- return true
- }`,
- want: []string{"Xwl4ViaAFDLrAmFX9g1blkUVC5dJj2he3a1tzkpJ4-PznQukQruRjqMFbEG73L92LJyBGMZ"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {appoptics}
- {AQAAABAAA zxsb8yzT0RbIJ1TAalB87LOVUcT1b4uEgvT4tXCcSqv_gcmlrx5aQRleHPDFKePjpHFof5J}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"zxsb8yzT0RbIJ1TAalB87LOVUcT1b4uEgvT4tXCcSqv_gcmlrx5aQRleHPDFKePjpHFof5J"},
- },
- {
- name: "invalid pattern",
- input: `
- func validateAppOpticsKey() bool {
- appopticsKey := "Xwl4ViaAFDLrAmFX9g1blkUVC5dJj2h:3a1tzkpJ43PznQukQruRjqMFbEG73L92LJyBGMZ"
- log.Println("Checking API key status...")
-
- if !isActive(appopticsKey) {
- log.Println("API key is inactive or invalid.")
- return false
- }
-
- log.Println("API key is valid and active.")
- return true
- }`,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/appsynergy/appsynergy.go b/pkg/detectors/appsynergy/appsynergy.go
deleted file mode 100644
index 79bcb67e895e..000000000000
--- a/pkg/detectors/appsynergy/appsynergy.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package appsynergy
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"appsynergy"}) + `\b([a-z0-9]{64})\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{"appsynergy"}
-}
-
-// FromData will find and optionally verify AppSynergy secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AppSynergy,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, secret string) (bool, error) {
- payload := strings.NewReader(`{"html":"Hello World ","filename":"HelloWorld.pdf"}`)
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://www.appsynergy.com/api?action=HTML2PDF&apiKey="+secret, payload)
- if err != nil {
- return false, err
- }
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- case http.StatusBadRequest:
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
- body := string(bodyBytes)
- if strings.Contains(body, "Invalid API Key") {
- return false, nil
- }
- return false, fmt.Errorf("status bad request invalid api key message not found: %d", res.StatusCode)
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AppSynergy
-}
-
-func (s Scanner) Description() string {
- return "AppSynergy is a platform for building cloud applications. AppSynergy API keys can be used to access and manage applications and data within the platform."
-}
diff --git a/pkg/detectors/appsynergy/appsynergy_integration_test.go b/pkg/detectors/appsynergy/appsynergy_integration_test.go
deleted file mode 100644
index 363af075ed58..000000000000
--- a/pkg/detectors/appsynergy/appsynergy_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package appsynergy
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAppSynergy_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APPSYNERGY")
- inactiveSecret := testSecrets.MustGetField("APPSYNERGY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a appsynergy secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AppSynergy,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a appsynergy secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AppSynergy,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AppSynergy.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AppSynergy.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/appsynergy/appsynergy_test.go b/pkg/detectors/appsynergy/appsynergy_test.go
deleted file mode 100644
index 12d63c806d69..000000000000
--- a/pkg/detectors/appsynergy/appsynergy_test.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package appsynergy
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAppSynergy_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func validateAppSynergyKey() bool {
- appSyneregyKey := "mg1pgwlndtq7rbk8i3kum344aso8ggp02ximdhsp8nsqasd3btxf84lz9ekfdpwo"
- log.Println("Checking API key status...")
-
- if !isActive(appSyneregyKey) {
- log.Println("API key is inactive or invalid.")
- return false
- }
-
- log.Println("API key is valid and active.")
- return true
- }`,
- want: []string{"mg1pgwlndtq7rbk8i3kum344aso8ggp02ximdhsp8nsqasd3btxf84lz9ekfdpwo"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {appsynergy}
- {AQAAABAAA ri1vn9m2otlg3yi8wwjegltc1t3bi4ljogg6c80onnrox2t9fuim6tce430fhklz}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"ri1vn9m2otlg3yi8wwjegltc1t3bi4ljogg6c80onnrox2t9fuim6tce430fhklz"},
- },
- {
- name: "invalid pattern",
- input: `
- func validateAppSynergyKey() bool {
- appSyneregyKey := "mg1pgwlndtq7rbk8i3kum_44aso8ggp02ximdhsp8nsqasd3btxf84lz9ekfdpwo"
- log.Println("Checking API key status...")
-
- if !isActive(appSyneregyKey) {
- log.Println("API key is inactive or invalid.")
- return false
- }
-
- log.Println("API key is valid and active.")
- return true
- }`,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/apptivo/apptivo.go b/pkg/detectors/apptivo/apptivo.go
deleted file mode 100644
index eb7882ecb2f3..000000000000
--- a/pkg/detectors/apptivo/apptivo.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package apptivo
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apptivo"}) + `\b([a-z0-9-]{36})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apptivo"}) + `\b([a-zA-Z0-9-]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"apptivo"}
-}
-
-// FromData will find and optionally verify Apptivo secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, idMatch := range idMatches {
- resIdMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Apptivo,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, resMatch, resIdMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch, resIdMatch)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, apiKey, accessKey string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://api.apptivo.com/app/dao/v6/leads?a=getConfigData&apiKey=%s&accessKey=%s", apiKey, accessKey), http.NoBody)
- if err != nil {
- return false, err
- }
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
- return strings.Contains(string(bodyBytes), `displayName`), nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Apptivo
-}
-
-func (s Scanner) Description() string {
- return "Apptivo is a cloud-based suite of business solutions, including CRM, project management, and more. Apptivo API keys can be used to access and manage these services programmatically."
-}
diff --git a/pkg/detectors/apptivo/apptivo_integration_test.go b/pkg/detectors/apptivo/apptivo_integration_test.go
deleted file mode 100644
index d86c9872724e..000000000000
--- a/pkg/detectors/apptivo/apptivo_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package apptivo
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestApptivo_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("APPTIVO")
- id := testSecrets.MustGetField("APPTIVO_KEY")
- inactiveSecret := testSecrets.MustGetField("APPTIVO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apptivo secret %s within apptivo %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apptivo,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a apptivo secret %s within but not valid apptivo %s", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Apptivo,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Apptivo.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Apptivo.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/apptivo/apptivo_test.go b/pkg/detectors/apptivo/apptivo_test.go
deleted file mode 100644
index 97b8fb79e98c..000000000000
--- a/pkg/detectors/apptivo/apptivo_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package apptivo
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestApptivo_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the apptivo API
- [DEBUG] Using apptivo Key=fox94at7-8dj92ns-cdxhag4470yqp0o2c8y
- [DEBUG] Using apptivo ID=C27YfQFKcUue8OxfEiAcqzrPVII-pb3V
- [INFO] Response received: 200 OK
- `,
- want: []string{"fox94at7-8dj92ns-cdxhag4470yqp0o2c8yC27YfQFKcUue8OxfEiAcqzrPVII-pb3V"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {apptivo o9qB77Q9cCXfuV-TWyCWUumiAbZc2Z7i}
- {apptivo AQAAABAAA juqc5-sw846p0cj43wy8eex6rr4v8-9oa3dh}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"juqc5-sw846p0cj43wy8eex6rr4v8-9oa3dho9qB77Q9cCXfuV-TWyCWUumiAbZc2Z7i"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the apptivo API
- [DEBUG] Using apptivo Key=fOx94aT7-8dj92ns-cdxhag4470yqp0o2c8y
- [DEBUG] Using apptivo ID=C27YfQF-cUue8OxfEiAcqzrPVII-pb3V
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/artifactory/artifactory.go b/pkg/detectors/artifactory/artifactory.go
deleted file mode 100644
index 856d90fa4b55..000000000000
--- a/pkg/detectors/artifactory/artifactory.go
+++ /dev/null
@@ -1,168 +0,0 @@
-package artifactory
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
- detectors.EndpointSetter
-}
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
- _ detectors.EndpointCustomizer = (*Scanner)(nil)
-
- defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b([a-zA-Z0-9]{64,73})\b`)
- URLPat = regexp.MustCompile(`\b([A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]\.jfrog\.io)`)
-
- invalidHosts = simple.NewCache[struct{}]()
-
- errNoHost = errors.New("no such host")
-)
-
-func (Scanner) CloudEndpoint() string { return "" }
-
-// 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{"artifactory", "jfrog.io"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Artifactory secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueTokens, uniqueUrls = make(map[string]struct{}), make(map[string]struct{})
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokens[match[1]] = struct{}{}
- }
-
- var foundUrls = make([]string, 0)
-
- for _, match := range URLPat.FindAllStringSubmatch(dataStr, -1) {
- foundUrls = append(foundUrls, match[1])
- }
-
- // add found + configured endpoints to the list
- for _, endpoint := range s.Endpoints(foundUrls...) {
- // if any configured endpoint has `https://` remove it because we append that during verification
- endpoint = strings.TrimPrefix(endpoint, "https://")
- uniqueUrls[endpoint] = struct{}{}
- }
-
- for token := range uniqueTokens {
- for url := range uniqueUrls {
- if invalidHosts.Exists(url) {
- delete(uniqueUrls, url)
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ArtifactoryAccessToken,
- Raw: []byte(token),
- RawV2: []byte(token + url),
- }
-
- if verify {
- isVerified, verificationErr := verifyArtifactory(ctx, s.getClient(), url, token)
- s1.Verified = isVerified
- if verificationErr != nil {
- if errors.Is(verificationErr, errNoHost) {
- invalidHosts.Set(url, struct{}{})
- continue
- }
-
- s1.SetVerificationError(verificationErr, token)
-
- if isVerified {
- s1.AnalysisInfo = map[string]string{
- "domain": url,
- "token": token,
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func verifyArtifactory(ctx context.Context, client *http.Client, resURLMatch, resMatch string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+resURLMatch+"/artifactory/api/system/ping", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("X-JFrog-Art-Api", resMatch)
-
- resp, err := client.Do(req)
- if err != nil {
- // lookup foo.jfrog.io: no such host
- if strings.Contains(err.Error(), "no such host") {
- return false, errNoHost
- }
-
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, err
- }
-
- if strings.Contains(string(body), "OK") {
- return true, nil
- }
-
- return false, nil
- case http.StatusUnauthorized, http.StatusForbidden, http.StatusFound: // 302 can occur if the url is incorrect
- // https://jfrog.com/help/r/jfrog-rest-apis/error-responses
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ArtifactoryAccessToken
-}
-
-func (s Scanner) Description() string {
- return "Artifactory is a repository manager that supports all major package formats. Artifactory access tokens can be used to authenticate and perform operations on repositories."
-}
diff --git a/pkg/detectors/artifactory/artifactory_integration_test.go b/pkg/detectors/artifactory/artifactory_integration_test.go
deleted file mode 100644
index 8dcccda9ce82..000000000000
--- a/pkg/detectors/artifactory/artifactory_integration_test.go
+++ /dev/null
@@ -1,134 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package artifactory
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestArtifactory_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ARTIFACTORY_TOKEN")
- inactiveSecret := testSecrets.MustGetField("ARTIFACTORY_INACTIVE")
- appURL := testSecrets.MustGetField("ARTIFACTORY_URL")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a artifactory secret %s and domain %s", secret, appURL)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ArtifactoryAccessToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a artifactory secret %s but not valid on endpoint %s", inactiveSecret, appURL)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ArtifactoryAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- tt.s.UseFoundEndpoints(true)
-
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Artifactory.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", "primarySecret")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Artifactory.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/artifactory/artifactory_test.go b/pkg/detectors/artifactory/artifactory_test.go
deleted file mode 100644
index 071d89ffd52b..000000000000
--- a/pkg/detectors/artifactory/artifactory_test.go
+++ /dev/null
@@ -1,168 +0,0 @@
-package artifactory
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestArtifactory_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- cloudEndpoint string
- useCloudEndpoint bool
- useFoundEndpoint bool
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the artifactory API
- [DEBUG] Using Key=cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg
- [INFO] rwxtOp.jfrog.io
- [INFO] Response received: 200 OK
- `,
- useCloudEndpoint: false,
- useFoundEndpoint: true,
- want: []string{"cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgrwxtOp.jfrog.io"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {artifactory}
- {AQAAABAAA KUd8GOVfcXnIv1nJ5qmnNzrqkLvseoPRMuwsdDVr9QthonFogtMaoJ3pgtO4eHXC}
- {HTTPnGQZ79vjWXze.jfrog.io}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- useCloudEndpoint: false,
- useFoundEndpoint: true,
- want: []string{"KUd8GOVfcXnIv1nJ5qmnNzrqkLvseoPRMuwsdDVr9QthonFogtMaoJ3pgtO4eHXCHTTPnGQZ79vjWXze.jfrog.io"},
- },
- {
- name: "valid pattern - with cloud endpoints",
- input: `
- [INFO] Sending request to the artifactory API
- [DEBUG] Using Key=cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg
- [INFO] Response received: 200 OK
- `,
- cloudEndpoint: "cloudendpoint.jfrog.io",
- useCloudEndpoint: true,
- useFoundEndpoint: false,
- want: []string{"cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgcloudendpoint.jfrog.io"},
- },
- {
- name: "valid pattern - with cloud and found endpoints",
- input: `
- [INFO] Sending request to the artifactory API
- [DEBUG] Using Key=cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg
- [INFO] rwxtOp.jfrog.io
- [INFO] Response received: 200 OK
- `,
- cloudEndpoint: "cloudendpoint.jfrog.io",
- useCloudEndpoint: true,
- useFoundEndpoint: true,
- want: []string{
- "cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgcloudendpoint.jfrog.io",
- "cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgrwxtOp.jfrog.io",
- },
- },
- {
- name: "valid pattern - with disabled found endpoints",
- input: `
- [INFO] Sending request to the artifactory API
- [DEBUG] Using Key=cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg
- [INFO] rwxtOp.jfrog.io
- [INFO] Response received: 200 OK
- `,
- cloudEndpoint: "cloudendpoint.jfrog.io",
- useCloudEndpoint: true,
- useFoundEndpoint: false,
- want: []string{
- "cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgcloudendpoint.jfrog.io",
- },
- },
- {
- name: "valid pattern - with https in configured endpoint",
- input: `
- [INFO] Sending request to the artifactory API
- [DEBUG] Using Key=cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg
- [INFO] Response received: 200 OK
- `,
- cloudEndpoint: "https://cloudendpoint.jfrog.io",
- useCloudEndpoint: true,
- useFoundEndpoint: false,
- want: []string{
- "cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgcloudendpoint.jfrog.io",
- },
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the artifactory API
- [DEBUG] Using Key=cmVmdGtuOjAxOjEODA_1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg
- [INFO] rwxtOp.jfrog.io
- [INFO] Response received: 200 OK
- `,
- useFoundEndpoint: true,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- // this detector use endpoint customizer interface so we need to enable them based on test case
- d.UseFoundEndpoints(test.useFoundEndpoint)
- d.UseCloudEndpoint(test.useCloudEndpoint)
- // if test case provide cloud endpoint use it
- if test.useCloudEndpoint && test.cloudEndpoint != "" {
- d.SetCloudEndpoint(test.cloudEndpoint)
- }
-
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/artsy/artsy.go b/pkg/detectors/artsy/artsy.go
deleted file mode 100644
index 89b6f8eaf726..000000000000
--- a/pkg/detectors/artsy/artsy.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package artsy
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"artsy"}) + `\b([0-9a-zA-Z]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"artsy"}) + `\b([0-9a-zA-Z]{20})\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{"artsy"}
-}
-
-// FromData will find and optionally verify Artsy secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
-
- resMatch := strings.TrimSpace(match[1])
-
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Artsy,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, resIdMatch, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, id, secret string) (bool, error) {
- // Reference: https://developers.artsy.net/v2/docs/authentication
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.artsy.net/api/tokens/xapp_token?client_id="+id+"&client_secret="+secret, http.NoBody)
- if err != nil {
- return false, err
- }
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
- switch res.StatusCode {
- case http.StatusCreated:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Artsy
-}
-
-func (s Scanner) Description() string {
- return "Artsy is an online platform for discovering, buying, and selling art. Artsy API keys can be used to access Artsy's services and data."
-}
diff --git a/pkg/detectors/artsy/artsy_integration_test.go b/pkg/detectors/artsy/artsy_integration_test.go
deleted file mode 100644
index 82a08adf2548..000000000000
--- a/pkg/detectors/artsy/artsy_integration_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package artsy
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestArtsy_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ARTSY")
- inactiveSecret := testSecrets.MustGetField("ARTSY_INACTIVE")
- id := testSecrets.MustGetField("ARTSY_CLIENTID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a artsy secret %s within artsyid %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Artsy,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a artsy secret %s within artsyid %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Artsy,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Artsy.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no rawv2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Artsy.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/artsy/artsy_test.go b/pkg/detectors/artsy/artsy_test.go
deleted file mode 100644
index 2d0bd184a915..000000000000
--- a/pkg/detectors/artsy/artsy_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package artsy
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestArtsy_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the artsy API
- [DEBUG] Using Key=rU0K6hwGw9AeANtXrZ8FQJT9jn4sRdlj
- [DEBUG] Using artsy ID=hvQ2fMvUPNczDCdmzi0i
- [INFO] Response received: 200 OK
- `,
- want: []string{"rU0K6hwGw9AeANtXrZ8FQJT9jn4sRdljhvQ2fMvUPNczDCdmzi0i"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {artsy Mbw4Tihfv1ttrspD1yXk}
- {artsy AQAAABAAA 3V4gtw8ZmDShAfzq2KKb3w0gZODnzxp7}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"3V4gtw8ZmDShAfzq2KKb3w0gZODnzxp7Mbw4Tihfv1ttrspD1yXk"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the artsy API
- [DEBUG] Using Key=rU0K6hwGw9AeANtX-Z8FQJT9jn4sRdlj
- [DEBUG] Using artsy ID=hvQ2fMvUPN_zDCdmzi0i
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/asanaoauth/asanaoauth.go b/pkg/detectors/asanaoauth/asanaoauth.go
deleted file mode 100644
index 83de2aef1173..000000000000
--- a/pkg/detectors/asanaoauth/asanaoauth.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package asanaoauth
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"asana"}) + `\b([a-z\/:0-9]{51})\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{"asana"}
-}
-
-// FromData will find and optionally verify AsanaOauth secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AsanaOauth,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- s1.AnalysisInfo = map[string]string{"key": resMatch}
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.asana.com/api/1.0/users/me", http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AsanaOauth
-}
-
-func (s Scanner) Description() string {
- return "Asana is a work management platform that helps teams organize, track, and manage their work. Asana OAuth tokens can be used to access and interact with Asana's API on behalf of a user."
-}
diff --git a/pkg/detectors/asanaoauth/asanaoauth_integration_test.go b/pkg/detectors/asanaoauth/asanaoauth_integration_test.go
deleted file mode 100644
index 063c55b0cede..000000000000
--- a/pkg/detectors/asanaoauth/asanaoauth_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package asanaoauth
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAsanaOauth_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ASANAOAUTH_TOKEN")
- inactiveSecret := testSecrets.MustGetField("ASANAOAUTH_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a asana secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AsanaOauth,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a asana secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AsanaOauth,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AsanaOauth.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].AnalysisInfo = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AsanaOauth.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/asanaoauth/asanaoauth_test.go b/pkg/detectors/asanaoauth/asanaoauth_test.go
deleted file mode 100644
index bea3efe94407..000000000000
--- a/pkg/detectors/asanaoauth/asanaoauth_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package asanaoauth
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAsanaOauth_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the asana API
- [DEBUG] Using Key=q5przi0tmp6xpo7rpsd0q:kl0qg:2gdj3jyumq04q9kcqk/qxdo
- [INFO] Response received: 200 OK
- `,
- want: []string{"q5przi0tmp6xpo7rpsd0q:kl0qg:2gdj3jyumq04q9kcqk/qxdo"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {asana}
- {AQAAABAAA omzmg54nn5wa21sh6qwg:dos10bfl1f6vnqcs9lcdwkbqb68gti}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"omzmg54nn5wa21sh6qwg:dos10bfl1f6vnqcs9lcdwkbqb68gti"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the asana API
- [DEBUG] Using Key=q5przi0tmP6xpo7rpsd0q;kl0qg:2gdj3jyumq04q9kcqk/qxdo
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go b/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go
deleted file mode 100644
index 7e3eb830dd82..000000000000
--- a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package asanapersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Updated pattern to handle both old and new token formats
- // Old format: [digits]/[16+ digits]:[32+ chars]
- // New format: [digits]/[16+ digits]/[16+ digits]:[32+ chars]
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"asana"}) + `\b([0-9]{1,}\/[0-9]{16,}(?:\/[0-9]{16,})?:[A-Za-z0-9]{32,})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"asana"}
-}
-
-// FromData will find and optionally verify AsanaPersonalAccessToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.asana.com/api/1.0/users/me", http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AsanaPersonalAccessToken
-}
-
-func (s Scanner) Description() string {
- return "Asana is a web and mobile application designed to help teams organize, track, and manage their work. Asana Personal Access Tokens can be used to access and modify data within Asana."
-}
diff --git a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_integration_test.go b/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_integration_test.go
deleted file mode 100644
index a33eefeb2b6f..000000000000
--- a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_integration_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package asanapersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAsanaPersonalAccessToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- testNewSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- oldFormatSecret := testSecrets.MustGetField("ASANA_PAT")
- newFormatSecret := testNewSecrets.MustGetField("ASANA_PAT_NEW")
- inactiveOldFormatSecret := testSecrets.MustGetField("ASANA_PAT_INACTIVE")
- inactiveNewFormatSecret := testNewSecrets.MustGetField("ASANA_PAT_NEW_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a asana secret %s within", oldFormatSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a asana secret %s within but unverified", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified - new format",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a asana secret %s within", newFormatSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified - new format",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a asana secret %s but unverified", inactiveNewFormatSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AsanaPersonalAccessToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AsanaPersonalAccessToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_test.go b/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_test.go
deleted file mode 100644
index 8e17f7185440..000000000000
--- a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package asanapersonalaccesstoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAsanaPersonalAccessToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - old format",
- input: `
- [INFO] Sending request to the asana API
- [DEBUG] Using Old Format asana Key=5947/1724908107002616220416212965:Yv3DoiSFhtsgUwN3AcnXWjK8zabQHKSHBRHpuNKVjz3oCcpyDIdXRm3GL4SUDkTMFoTb
- [ERROR] Response received: 400 BadRequest
- [DEBUG] Using new format asana Key=7/9823746598123746/8923746598123456:7f1a3c9be84d2a6c4e7d9c32bf1e7f88
- [INFO] Response received: 200 OK
- `,
- want: []string{
- "5947/1724908107002616220416212965:Yv3DoiSFhtsgUwN3AcnXWjK8zabQHKSHBRHpuNKVjz3oCcpyDIdXRm3GL4SUDkTMFoTb",
- "7/9823746598123746/8923746598123456:7f1a3c9be84d2a6c4e7d9c32bf1e7f88",
- },
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {asana}
- {AQAAABAAA 891435852083139681602524390768273271357927849104481/366163755073364840345913922341185329292536814045275090976491644844014597476863956806652784056747/17480879147700616278211801017829125:Hb7meGPLBz7jH7e1fiHetN355omiO9Zt8fewjSOX4qfUoWDzvvlNA6lBx9rNuR8EAEElmtmmL9J4ilO8m2D56n}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"891435852083139681602524390768273271357927849104481/366163755073364840345913922341185329292536814045275090976491644844014597476863956806652784056747/17480879147700616278211801017829125:Hb7meGPLBz7jH7e1fiHetN355omiO9Zt8fewjSOX4qfUoWDzvvlNA6lBx9rNuR8EAEElmtmmL9J4ilO8m2D56n"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the asana API
- [DEBUG] Using Old Format asana Key=5947766540345/172490810700261:Yv3DoiSFhjK8zabQHKSHBRHpuNKVjz3oCcpyDIdXRm3GL4SUDkTMFoTbRDCHe8tTBHxdtoXItn
- [ERROR] Response received: 400 BadRequest
- [DEBUG] Using new format asana Key=7/98237465/8923746598156:7f1a3c9be84d2a6c4e7d9c32bf1e7f88
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/assemblyai/assemblyai.go b/pkg/detectors/assemblyai/assemblyai.go
deleted file mode 100644
index 73441ccbbd7c..000000000000
--- a/pkg/detectors/assemblyai/assemblyai.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package assemblyai
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"assemblyai"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"assemblyai"}
-}
-
-// FromData will find and optionally verify Assemblyai secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AssemblyAI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.assemblyai.com/v2/transcript", http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", token)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AssemblyAI
-}
-
-func (s Scanner) Description() string {
- return "AssemblyAI is a service that provides speech-to-text transcription. AssemblyAI keys can be used to access and utilize the transcription services provided by AssemblyAI."
-}
diff --git a/pkg/detectors/assemblyai/assemblyai_integration_test.go b/pkg/detectors/assemblyai/assemblyai_integration_test.go
deleted file mode 100644
index f042fb413469..000000000000
--- a/pkg/detectors/assemblyai/assemblyai_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package assemblyai
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAssemblyai_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ASSEMBLYAI")
- inactiveSecret := testSecrets.MustGetField("ASSEMBLYAI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a assemblyai secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AssemblyAI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a assemblyai secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AssemblyAI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Assemblyai.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Assemblyai.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/assemblyai/assemblyai_test.go b/pkg/detectors/assemblyai/assemblyai_test.go
deleted file mode 100644
index 2d00f673abdf..000000000000
--- a/pkg/detectors/assemblyai/assemblyai_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package assemblyai
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAssemblyAI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using assemblyai Key=mlhekyjhs96mx0r2cxbzky4jzr83fw1q
- [INFO] Response received: 200 OK
- `,
- want: []string{"mlhekyjhs96mx0r2cxbzky4jzr83fw1q"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {assemblyai}
- {AQAAABAAA s0c8a99g0w6qbwybdxn4uowzemk1xlca}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"s0c8a99g0w6qbwybdxn4uowzemk1xlca"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using assemblyai Key=Mlhekyjzr83fw1qr2cxbzky4jzr83f1q
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/atera/atera.go b/pkg/detectors/atera/atera.go
deleted file mode 100644
index d18650490b11..000000000000
--- a/pkg/detectors/atera/atera.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package atera
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"atera"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"atera"}
-}
-
-// FromData will find and optionally verify Atera secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Atera,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.atera.com/api/v3/alerts", http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("X-API-KEY", token)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Atera
-}
-
-func (s Scanner) Description() string {
- return "Atera is an IT management platform that provides remote monitoring and management for IT professionals. Atera API keys can be used to interact with the Atera API to manage alerts, tickets, devices, and more."
-}
diff --git a/pkg/detectors/atera/atera_integration_test.go b/pkg/detectors/atera/atera_integration_test.go
deleted file mode 100644
index ee90eafbc080..000000000000
--- a/pkg/detectors/atera/atera_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package atera
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAtera_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ATERA")
- inactiveSecret := testSecrets.MustGetField("ATERA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find an atera secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Atera,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find an atera secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Atera,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Atera.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Atera.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/atera/atera_test.go b/pkg/detectors/atera/atera_test.go
deleted file mode 100644
index f6e46d158d08..000000000000
--- a/pkg/detectors/atera/atera_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package atera
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAtera_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the atera API
- [DEBUG] Using Key=yoo3d5pu3t4zxd6x1vhk7ykmjqarbsv1
- [INFO] Response received: 200 OK
- `,
- want: []string{"yoo3d5pu3t4zxd6x1vhk7ykmjqarbsv1"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {atera}
- {AQAAABAAA uvyn0qy0ec96pgxfr2s3i4bqv1znl7yg}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"uvyn0qy0ec96pgxfr2s3i4bqv1znl7yg"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the atera API
- [DEBUG] Using Key=yOO3d5pu3t4zxd6x1vhk7ykmjqarbs_1
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/atlassian/v1/atlassian.go b/pkg/detectors/atlassian/v1/atlassian.go
deleted file mode 100644
index f7c2d612ff00..000000000000
--- a/pkg/detectors/atlassian/v1/atlassian.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package atlassian
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-func (s Scanner) Version() int { return 1 }
-
-type OrgRes struct {
- Data []struct {
- Attributes struct {
- Name string `json:"name"`
- } `json:"attributes"`
- } `json:"data"`
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"atlassian"}) + `\b([a-zA-Z-0-9]{24})\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{"atlassian"}
-}
-
-// Description returns a description for the result being detected
-func (s Scanner) Description() string {
- return "Atlassian provides tools for software development, project management, and content management. Atlassian API keys can be used to access and manage these tools and services."
-}
-
-// FromData will find and optionally verify Atlassian secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Atlassian,
- Raw: []byte(match),
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/atlassian/",
- "version": fmt.Sprintf("%d", s.Version()),
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, orgResponse, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- if orgResponse != nil {
- s1.ExtraData["Organization"] = orgResponse.Data[0].Attributes.Name
- }
- s1.SetVerificationError(verificationErr, match)
-
- if isVerified {
- s1.AnalysisInfo = map[string]string{
- "key": match,
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, *OrgRes, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.atlassian.com/admin/v1/orgs", nil)
- if err != nil {
- return false, nil, err
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- // If the endpoint returns useful information, we can return it as a map.
- var orgResponse OrgRes
- if err = json.NewDecoder(res.Body).Decode(&orgResponse); err != nil {
- return false, nil, err
- }
- return true, &orgResponse, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Atlassian
-}
diff --git a/pkg/detectors/atlassian/v1/atlassian_integration_test.go b/pkg/detectors/atlassian/v1/atlassian_integration_test.go
deleted file mode 100644
index 4d5633928b7d..000000000000
--- a/pkg/detectors/atlassian/v1/atlassian_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package atlassian
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAtlassian_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ATLASSIAN")
- inactiveSecret := testSecrets.MustGetField("ATLASSIAN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a atlassian secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Atlassian,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a atlassian secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Atlassian,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a atlassian secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Atlassian,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a atlassian secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Atlassian,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Atlassian.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "ExtraData")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Atlassian.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/atlassian/v1/atlassian_test.go b/pkg/detectors/atlassian/v1/atlassian_test.go
deleted file mode 100644
index 335f4bdaab8c..000000000000
--- a/pkg/detectors/atlassian/v1/atlassian_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package atlassian
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAtlassian_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the atlassian API
- [DEBUG] Using Key=aB1cD2eF3gH4iJ5kL6mN7oP8
- [INFO] Response received: 200 OK
- `,
- want: []string{"aB1cD2eF3gH4iJ5kL6mN7oP8"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {atlassian}
- {AQAAABAAA r6RkiQao3PgqY9MOKtonpJdU}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"r6RkiQao3PgqY9MOKtonpJdU"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/atlassian/v2/atlassian.go b/pkg/detectors/atlassian/v2/atlassian.go
deleted file mode 100644
index 0d9f32495131..000000000000
--- a/pkg/detectors/atlassian/v2/atlassian.go
+++ /dev/null
@@ -1,150 +0,0 @@
-package atlassian
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-func (s Scanner) Version() int { return 2 }
-
-type OrgRes struct {
- Data []struct {
- Attributes struct {
- Name string `json:"name"`
- } `json:"attributes"`
- } `json:"data"`
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
-
- // Example: ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A
- keyPat = regexp.MustCompile(`\b(ATCTT3xFfG[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8})\b`)
- // Example: 123e4567-e89b-12d3-a456-426614174000
- organizationIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{"org", "id"}) + `\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\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{"ATCTT3xFfG"}
-}
-
-// Description returns a description for the result being detected
-func (s Scanner) Description() string {
- return "Atlassian is a software company that provides tools for project management, software development, and collaboration. Atlassian tokens can be used to access and manage these tools and services."
-}
-
-// FromData will find and optionally verify Atlassian secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- uniqueOrgIdMatches := make(map[string]struct{})
- for _, match := range organizationIdPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueOrgIdMatches[match[1]] = struct{}{}
- }
- if len(uniqueOrgIdMatches) == 0 {
- // we only need an org ID to pass into AnalysisInfo
- // if we don't find one, we can still verify the key
- // we can add a dummy entry here just to make sure a result is returned
- uniqueOrgIdMatches[""] = struct{}{}
- }
-
- for match := range uniqueMatches {
- for orgId := range uniqueOrgIdMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Atlassian,
- Raw: []byte(match),
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/atlassian/",
- "version": fmt.Sprintf("%d", s.Version()),
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, orgResponse, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- if orgResponse != nil && len(orgResponse.Data) > 0 {
- s1.ExtraData["Organization"] = orgResponse.Data[0].Attributes.Name
- }
- s1.SetVerificationError(verificationErr, match)
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{
- "key": match,
- }
- if orgId != "" {
- s1.AnalysisInfo["organization_id"] = orgId
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, *OrgRes, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.atlassian.com/admin/v1/orgs", nil)
- if err != nil {
- return false, nil, err
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- // If the endpoint returns useful information, we can return it as a map.
- var orgResponse OrgRes
- if err = json.NewDecoder(res.Body).Decode(&orgResponse); err != nil {
- return false, nil, err
- }
- return true, &orgResponse, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Atlassian
-}
diff --git a/pkg/detectors/atlassian/v2/atlassian_integration_test.go b/pkg/detectors/atlassian/v2/atlassian_integration_test.go
deleted file mode 100644
index efe0e0d9d034..000000000000
--- a/pkg/detectors/atlassian/v2/atlassian_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package atlassian
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAtlassian_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ATLASSIAN")
- inactiveSecret := testSecrets.MustGetField("ATLASSIAN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a atlassian secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Atlassian,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a atlassian secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Atlassian,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a atlassian secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Atlassian,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a atlassian secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Atlassian,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Atlassian.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "ExtraData", "primarySecret", "AnalysisInfo")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Atlassian.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/atlassian/v2/atlassian_test.go b/pkg/detectors/atlassian/v2/atlassian_test.go
deleted file mode 100644
index 96a4a5aa380e..000000000000
--- a/pkg/detectors/atlassian/v2/atlassian_test.go
+++ /dev/null
@@ -1,163 +0,0 @@
-package atlassian
-
-import (
- "context"
- "fmt"
- "net/http"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
- "gopkg.in/h2non/gock.v1"
-)
-
-func TestAtlassian_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the atlassian API
- [DEBUG] Using Key=ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A
- [INFO] Response received: 200 OK
- `,
- want: []string{"ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {98651}
- {AQAAABAAA ATCTT3xFfGXc59Vkq40qLX=iEOIrJRZ}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"ATCTT3xFfGXc59Vkq40qLX=iEOIrJRZ"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
-
-// TestAtlassian_AnalysisInfo_KeyAndOrgId tests if both the key and organization id are populated into AnalysisInfo
-// given that they are present in the input data chunk
-func TestAtlassian_AnalysisInfo_KeyAndOrgId(t *testing.T) {
- client := common.SaneHttpClient()
- d := Scanner{client: client}
-
- key := "ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A"
- orgId := "123j4567-e89b-12d3-a456-426614174000"
-
- defer gock.Off()
- defer gock.RestoreClient(client)
- gock.InterceptClient(client)
- gock.New("https://api.atlassian.com").
- Get("/admin/v1/orgs").
- MatchHeader("Accept", "application/json").
- MatchHeader("Authorization", fmt.Sprintf("Bearer %s", key)).
- Reply(http.StatusOK).
- JSON(map[string]any{
- "Data": []map[string]any{},
- })
-
- t.Run("key and organization id both present", func(t *testing.T) {
- input := fmt.Sprintf(`
- [INFO] Sending request to the atlassian API
- [DEBUG] Using Key=%s
- [DEBUG] Using Organization ID=%s
- [INFO] Response received: 200 OK
- `, key, orgId)
-
- results, err := d.FromData(context.Background(), true, []byte(input))
- require.NoError(t, err)
- require.Len(t, results, 1, "mismatch in result count: expected %d, got %d", 1, len(results))
- result := results[0]
- require.NotNil(t, result.AnalysisInfo, "AnalysisInfo is nil")
-
- assert.Equal(t, key, result.AnalysisInfo["key"], "mismatch in key")
- assert.Equal(t, orgId, result.AnalysisInfo["organization_id"], "mismatch in organization_id")
- })
-}
-
-// TestAtlassian_AnalysisInfo_KeyOnly tests if only key is populated into AnalysisInfo
-// given that only the key and no organization_id is present in the input data chunk
-func TestAtlassian_AnalysisInfo_KeyOnly(t *testing.T) {
- client := common.SaneHttpClient()
- d := Scanner{client: client}
-
- key := "ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A"
-
- defer gock.Off()
- defer gock.RestoreClient(client)
- gock.InterceptClient(client)
- gock.New("https://api.atlassian.com").
- Get("/admin/v1/orgs").
- MatchHeader("Accept", "application/json").
- MatchHeader("Authorization", fmt.Sprintf("Bearer %s", key)).
- Reply(http.StatusOK).
- JSON(map[string]any{
- "Data": []map[string]any{},
- })
- t.Run("only key present", func(t *testing.T) {
-
- input := fmt.Sprintf(`
- [INFO] Sending request to the atlassian API
- [DEBUG] Using Key=%s
- [INFO] Response received: 200 OK
- `, key)
-
- results, err := d.FromData(context.Background(), true, []byte(input))
- require.NoError(t, err)
- require.Len(t, results, 1, "mismatch in result count: expected %d, got %d", 1, len(results))
- result := results[0]
- require.NotNil(t, result.AnalysisInfo, "AnalysisInfo is nil")
-
- assert.Equal(t, key, result.AnalysisInfo["key"], "mismatch in key")
- })
-}
diff --git a/pkg/detectors/audd/audd.go b/pkg/detectors/audd/audd.go
deleted file mode 100644
index d60c9cc65c97..000000000000
--- a/pkg/detectors/audd/audd.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package audd
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"audd"}) + `\b([a-z0-9-]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"audd"}
-}
-
-// FromData will find and optionally verify Audd secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Audd,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.audd.io/setCallbackUrl/?api_token=%s&url=https://yourwebsite.com/callbacks_handler/", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err == nil {
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"status":"success"`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Audd
-}
-
-func (s Scanner) Description() string {
- return "Audd is a music recognition service. Audd API tokens can be used to access the Audd API services for recognizing music and obtaining metadata."
-}
diff --git a/pkg/detectors/audd/audd_integration_test.go b/pkg/detectors/audd/audd_integration_test.go
deleted file mode 100644
index 4f1424150edf..000000000000
--- a/pkg/detectors/audd/audd_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package audd
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAudd_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AUDD")
- inactiveSecret := testSecrets.MustGetField("AUDD_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a audd secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Audd,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a audd secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Audd,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Audd.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Audd.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/audd/audd_test.go b/pkg/detectors/audd/audd_test.go
deleted file mode 100644
index 931f04773d56..000000000000
--- a/pkg/detectors/audd/audd_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package audd
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAudd_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the audd API
- [DEBUG] Using Key=60fzzcspq2balbxn7f3hi2nvg3h07h4z
- [INFO] Response received: 200 OK
- `,
- want: []string{"60fzzcspq2balbxn7f3hi2nvg3h07h4z"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {audd}
- {AQAAABAAA uv2kv0x8htfhgnugnsbys7a8oyky5ryb}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"uv2kv0x8htfhgnugnsbys7a8oyky5ryb"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the audd API
- [DEBUG] Using Key=60fzzcspq2balbxn7f3hi2nvg3h07h4zY
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/auth0managementapitoken/auth0managementapitoken.go b/pkg/detectors/auth0managementapitoken/auth0managementapitoken.go
deleted file mode 100644
index bffa672e9349..000000000000
--- a/pkg/detectors/auth0managementapitoken/auth0managementapitoken.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package auth0managementapitoken
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.MaxSecretSizeProvider = (*Scanner)(nil)
-
-var (
- client = detectors.DetectorHttpClientWithLocalAddresses
-
- // long jwt token but note this is default 8640000 seconds = 24 hours but could be set to maximum 2592000 seconds = 720 hours = 30 days
- // at https://manage.auth0.com/dashboard/us/dev-63memjo3/apis/management/explorer
- managementAPITokenPat = regexp.MustCompile(`\b(ey[a-zA-Z0-9._-]+)\b`)
- domainPat = regexp.MustCompile(`([a-zA-Z0-9\-]{2,16}\.[a-zA-Z0-9_-]{2,3}\.auth0\.com)`) // could be part of url
-)
-
-// 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{"auth0"} }
-
-const maxSecretSize = 5000
-
-func (Scanner) MaxSecretSize() int64 { return maxSecretSize }
-
-// FromData will find and optionally verify Auth0ManagementApiToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- managementAPITokenMatches := managementAPITokenPat.FindAllStringSubmatch(dataStr, -1)
- domainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, managementApiTokenMatch := range managementAPITokenMatches {
- managementAPITokenRes := strings.TrimSpace(managementApiTokenMatch[1])
- if len(managementAPITokenRes) < 2000 || len(managementAPITokenRes) > 5000 {
- continue
- }
-
- for _, domainMatch := range domainMatches {
- domainRes := strings.TrimSpace(domainMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Auth0ManagementApiToken,
- Redacted: domainRes,
- Raw: []byte(managementAPITokenRes),
- RawV2: []byte(managementAPITokenRes + domainRes),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, managementAPITokenRes, domainRes)
- s1.Verified = isVerified
- s1.SetVerificationError(err, managementAPITokenRes)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token, domain string) (bool, error) {
- /*
- curl -H "Authorization: Bearer $token" https://domain/api/v2/users
- Reference: https://auth0.com/docs/api/management/v2/users/get-users
- */
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+domain+"/api/v2/users", http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
- switch res.StatusCode {
- case http.StatusOK, http.StatusForbidden:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Auth0ManagementApiToken
-}
-
-func (s Scanner) Description() string {
- return "Auth0 provides authentication and authorization as a service. Auth0 Management API tokens can be used to manage users, roles, permissions, and other aspects of the Auth0 service."
-}
diff --git a/pkg/detectors/auth0managementapitoken/auth0managementapitoken_integration_test.go b/pkg/detectors/auth0managementapitoken/auth0managementapitoken_integration_test.go
deleted file mode 100644
index dbbdf270886c..000000000000
--- a/pkg/detectors/auth0managementapitoken/auth0managementapitoken_integration_test.go
+++ /dev/null
@@ -1,127 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package auth0managementapitoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAuth0ManagementApiToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- // use 2592000 for 30 days, this is the maximum allowed
- managementApiToken := testSecrets.MustGetField("AUTH0_MANAGEMENT_APITOKEN")
- inactiveManagementApiToken := testSecrets.MustGetField("AUTH0_MANAGEMENT_APITOKEN_INACTIVE")
- domain := testSecrets.MustGetField("AUTH0_MANAGEMENT_DOMAIN")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a auth0 secret %s domain %s", managementApiToken, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Auth0ManagementApiToken,
- RawV2: []byte(managementApiToken + domain),
- Redacted: domain,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a auth0 secret %s domain https://%s/oauth/token within but not valid", inactiveManagementApiToken, domain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Auth0ManagementApiToken,
- RawV2: []byte(inactiveManagementApiToken + domain),
- Redacted: domain,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Auth0ManagementApiToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Auth0ManagementApiToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/auth0managementapitoken/auth0managementapitoken_test.go b/pkg/detectors/auth0managementapitoken/auth0managementapitoken_test.go
deleted file mode 100644
index 46eee7bfc201..000000000000
--- a/pkg/detectors/auth0managementapitoken/auth0managementapitoken_test.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package auth0managementapitoken
-
-import (
- "context"
- "fmt"
- "math/rand"
- "strings"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- // TODO: Refactor the fake token generation if possible
- validPattern = generateRandomString() // this has the exact token string only which can be used in want too
-)
-
-func TestAuth0ManagementApitToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: makeFakeTokenString(validPattern, "Truffle-security.org.auth0.com"),
- want: []string{validPattern + "Truffle-security.org.auth0.com"},
- },
- {
- name: "invalid pattern",
- input: `
- auth0_credentials:
- apiToken: eywT2nGMZwOcbsUVBwfiRPEl8P_wnmo6XfdUoGVwxDfOSjNyqhYqFdi.KojZZOM8Ox
- domain: Truffle-security.org.auth0.com
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
-
-// makeFakeTokenString take a string token as parameter and make a string that looks like a token for testing
-func makeFakeTokenString(token, domain string) string {
- return fmt.Sprintf("auth0:\n apiToken: %s \n domain: %s", token, domain)
-}
-
-// generateRandomString generates exactly 2001 char string for a fake token to pass the check in detector for testing
-func generateRandomString() string {
- const length = 2001
- const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
- const charsetWithBoundaryChars = charset + ".-"
-
- random := rand.New(rand.NewSource(time.Now().UnixNano()))
-
- var builder strings.Builder
- builder.Grow(length)
-
- for i := 0; i < length-1; i++ {
- randomChar := charsetWithBoundaryChars[random.Intn(len(charset))]
- builder.WriteByte(randomChar)
- }
-
- // ensure last character is not boundary character
- lastChar := charset[random.Intn(len(charset))]
- builder.WriteByte(lastChar)
-
- // append ey in start as the token must start with 'ey'
- return fmt.Sprintf("ey%s", builder.String())
-}
diff --git a/pkg/detectors/auth0oauth/auth0oauth.go b/pkg/detectors/auth0oauth/auth0oauth.go
deleted file mode 100644
index 543fa060be8e..000000000000
--- a/pkg/detectors/auth0oauth/auth0oauth.go
+++ /dev/null
@@ -1,148 +0,0 @@
-package auth0oauth
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = detectors.DetectorHttpClientWithLocalAddresses
-
- clientIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{"auth0"}) + `\b([a-zA-Z0-9_-]{32,60})\b`)
- clientSecretPat = regexp.MustCompile(`\b([a-zA-Z0-9_-]{64,})\b`)
- domainPat = regexp.MustCompile(`\b([a-zA-Z0-9][a-zA-Z0-9._-]*auth0\.com)\b`) // could be part of url
-)
-
-// 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{"auth0"}
-}
-
-// FromData will find and optionally verify Auth0oauth secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- uniqueDomainMatches := make(map[string]struct{})
- uniqueClientIDs := make(map[string]struct{})
- uniqueSecrets := make(map[string]struct{})
- for _, m := range domainPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueDomainMatches[strings.TrimSpace(m[1])] = struct{}{}
- }
- for _, m := range clientIdPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueClientIDs[strings.TrimSpace(m[1])] = struct{}{}
- }
- for _, m := range clientSecretPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueSecrets[strings.TrimSpace(m[1])] = struct{}{}
- }
-
- for clientIdRes := range uniqueClientIDs {
- for clientSecretRes := range uniqueSecrets {
- for domainRes := range uniqueDomainMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Auth0oauth,
- Redacted: clientIdRes,
- Raw: []byte(clientSecretRes),
- RawV2: []byte(clientIdRes + clientSecretRes),
- }
-
- if verify {
-
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, err := verifyTuple(ctx, client, domainRes, clientIdRes, clientSecretRes)
- if err != nil {
- s1.SetVerificationError(err, clientIdRes)
- }
- s1.Verified = isVerified
- }
-
- results = append(results, s1)
- }
- }
- }
-
- return results, nil
-}
-
-func verifyTuple(ctx context.Context, client *http.Client, domainRes, clientId, clientSecret string) (bool, error) {
- /*
- curl --request POST \
- --url 'https://YOUR_DOMAIN/oauth/token' \
- --header 'content-type: application/x-www-form-urlencoded' \
- --data 'grant_type=authorization_code&client_id=W44JmL3qD6LxHeEJyKe9lMuhcwvPOaOq&client_secret=YOUR_CLIENT_SECRET&code=AUTHORIZATION_CODE&redirect_uri=undefined'
- */
-
- data := url.Values{}
- data.Set("grant_type", "authorization_code")
- data.Set("client_id", clientId)
- data.Set("client_secret", clientSecret)
- data.Set("code", "AUTHORIZATION_CODE")
- data.Set("redirect_uri", "undefined")
-
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://"+domainRes+"/oauth/token", strings.NewReader(data.Encode())) // URL-encoded payload
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- // This condition will never meet due to invalid request body
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- case http.StatusForbidden:
- // cross check about 'invalid_grant' or 'unauthorized_client' in response body
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, err
- }
- bodyStr := string(bodyBytes)
- if strings.Contains(bodyStr, "invalid_grant") || strings.Contains(bodyStr, "unauthorized_client") {
- return true, nil
- }
- return false, nil
- case http.StatusNotFound:
- // domain does not exists - 404 not found
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Auth0oauth
-}
-
-func (s Scanner) Description() string {
- return "Auth0 is a service designed to handle authentication and authorization for users. Oauth API keys can be used to impersonate applications and other things related to Auth0's API"
-}
diff --git a/pkg/detectors/auth0oauth/auth0oauth_integeration_test.go b/pkg/detectors/auth0oauth/auth0oauth_integeration_test.go
deleted file mode 100644
index 2a678030136e..000000000000
--- a/pkg/detectors/auth0oauth/auth0oauth_integeration_test.go
+++ /dev/null
@@ -1,169 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package auth0oauth
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAuth0oauth_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- domain := testSecrets.MustGetField("AUTH0_DOMAIN")
- clientId := testSecrets.MustGetField("AUTH0_CLIENT_ID")
- clientSecret := testSecrets.MustGetField("AUTH0_CLIENT_SECRET")
-
- domainUnauthorized := testSecrets.MustGetField("AUTH0_DOMAIN_UNAUTHORIZED")
- clientIdUnauthorized := testSecrets.MustGetField("AUTH0_CLIENT_ID_UNAUTHORIZED")
- clientSecretUnauthorized := testSecrets.MustGetField("AUTH0_CLIENT_SECRET_UNAUTHORIZED")
-
- notFoundDomain := testSecrets.MustGetField("AUTH0_DOMAIN_NOT_FOUND")
- inactiveClientSecret := testSecrets.MustGetField("AUTH0_CLIENT_SECRET_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a auth0 client id %s client secret %s domain %s", clientId, clientSecret, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Auth0oauth,
- Redacted: clientId,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified but unauthorized",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a auth0 client id %s client secret %s domain %s", clientIdUnauthorized, clientSecretUnauthorized, domainUnauthorized)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Auth0oauth,
- Redacted: clientIdUnauthorized,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a auth0 client id %s client secret %s domain https://%s/oauth/token within but not valid", clientId, inactiveClientSecret, domain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Auth0oauth,
- Redacted: clientId,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- {
- name: "domain does not exists",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a auth0 client id %s client secret %s domain %s", clientId, clientSecret, notFoundDomain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Auth0oauth,
- Redacted: clientId,
- Verified: false,
- },
- },
- 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("Auth0oauth.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no raw v2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Auth0oauth.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/auth0oauth/auth0oauth_test.go b/pkg/detectors/auth0oauth/auth0oauth_test.go
deleted file mode 100644
index 1f788240565a..000000000000
--- a/pkg/detectors/auth0oauth/auth0oauth_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package auth0oauth
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAuth0oAuth_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- # do not share these credentials
- auth0_credentials file:
- auth0_clientID: kYWr_tL4eYBtqIIvKfSf2-e4T9Cw1CtwE8ufoESVBB7Hi1U
- secret: rXwGtKCleBsaUfpchggQEAy_yhzWnqv4_GzJivBif85bqiJi3ZA63DAauoJ2PF27fvS-MBqIYgxH0vZaL1s5314lgPDLqHXjZsY59PSew63A_L6rySqcy5J3rFcGcpdeSQ_tTx1kCXOZY_JUy
- domain: 9-KhTIdSopSaMQ2v1YxdFEJN-HNgt7Mn7E8xkfQNqd51AzSGQu2yRaFauth0.com
- `,
- want: []string{"kYWr_tL4eYBtqIIvKfSf2-e4T9Cw1CtwE8ufoESVBB7Hi1UrXwGtKCleBsaUfpchggQEAy_yhzWnqv4_GzJivBif85bqiJi3ZA63DAauoJ2PF27fvS-MBqIYgxH0vZaL1s5314lgPDLqHXjZsY59PSew63A_L6rySqcy5J3rFcGcpdeSQ_tTx1kCXOZY_JUy"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {auth0 rP_yIAV6HD3Oe4zr6KawRXGbq6UCWbeC1kbjQkVhqG4vcLCc2}
- {AQAAABAAA 1PMNVllg_WHl2OGdPLSs73Z1NHjQ85nafV2qqKbQivoqEz4RSo6MFBoNxF-XqFKjEyt6WJfZvAslDPrwY-B-MLsN13rgxRrAiFw9d8Rl1e0uC0FCNDC5EALR9kq7cs4Atz_Dv4r5YT8drkV1_T5HMjH8SJb2B-jD}
- {kXFuauth0.com}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"rP_yIAV6HD3Oe4zr6KawRXGbq6UCWbeC1kbjQkVhqG4vcLCc21PMNVllg_WHl2OGdPLSs73Z1NHjQ85nafV2qqKbQivoqEz4RSo6MFBoNxF-XqFKjEyt6WJfZvAslDPrwY-B-MLsN13rgxRrAiFw9d8Rl1e0uC0FCNDC5EALR9kq7cs4Atz_Dv4r5YT8drkV1_T5HMjH8SJb2B-jD"},
- },
- {
- name: "invalid pattern",
- input: `
- # do not share these credentials
- auth0_credentials file:
- auth0_clientID: e4T9Cw1CtwE8ufoESVBB7Hi1U-e4T9Cw1CtwE8ufoESVBB7Hi1U
- secret: MBqIYgxH0vZaL1s5314lgPDLqHX^ZsY59PSew63A_L6rySqcy5J3rFcGcpdeSQ_+tTx1kCXOZY_JUy-rXwGtKCleBsaUfpchggQEAy_yhzWnqv4_GzJivBif85bqiJi3ZA63DAauoJ2PF27fvS
- domain: 9-KhTIdSopSaMQ2v1YxdFEJN#qd51AzSGQu2yRaFauth1.com
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/autodesk/autodesk.go b/pkg/detectors/autodesk/autodesk.go
deleted file mode 100644
index 43f391abf5f1..000000000000
--- a/pkg/detectors/autodesk/autodesk.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package autodesk
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"autodesk"}) + `\b([0-9A-Za-z]{32})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"autodesk"}) + `\b([0-9A-Za-z]{16})\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{"autodesk"}
-}
-
-// FromData will find and optionally verify Autodesk secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, secretMatch := range secretMatches {
- resSecret := strings.TrimSpace(secretMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Autodesk,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resSecret),
- }
-
- if verify {
- payload := strings.NewReader(fmt.Sprintf(`grant_type=client_credentials&client_id=%s&client_secret=%s`, resMatch, resSecret))
- req, err := http.NewRequestWithContext(ctx, "POST", "https://developer.api.autodesk.com/authentication/v1/authenticate", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Autodesk
-}
-
-func (s Scanner) Description() string {
- return "Autodesk provides software services for design and engineering. Autodesk API keys can be used to access and modify data within Autodesk services."
-}
diff --git a/pkg/detectors/autodesk/autodesk_integration_test.go b/pkg/detectors/autodesk/autodesk_integration_test.go
deleted file mode 100644
index d3d90c83c811..000000000000
--- a/pkg/detectors/autodesk/autodesk_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package autodesk
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAutodesk_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- id := testSecrets.MustGetField("AUTODESK_ID")
- secret := testSecrets.MustGetField("AUTODESK_SECRET")
- inactiveID := testSecrets.MustGetField("AUTODESK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a autodesk secret %s within autodesk id %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Autodesk,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a autodesk secret %s within autodesk id %s but not valid", secret, inactiveID)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Autodesk,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Autodesk.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Autodesk.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/autodesk/autodesk_test.go b/pkg/detectors/autodesk/autodesk_test.go
deleted file mode 100644
index 85be1329324b..000000000000
--- a/pkg/detectors/autodesk/autodesk_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package autodesk
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAutoDesk_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using autodesk Key=2j8Rl67MjoMruYfyIBgGzy2pxcxIQfet
- [DEBUG] Using autodesk Secret=rHfzZhsSRruLM3Fn
- [INFO] Response received: 200 OK
- `,
- want: []string{"2j8Rl67MjoMruYfyIBgGzy2pxcxIQfetrHfzZhsSRruLM3Fn"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {autodesk 0xjHuuRZc8n0YS6MGd8e3OakAySlK27q}
- {autodesk AQAAABAAA 0TvJm15Ew8KADWTN}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"0xjHuuRZc8n0YS6MGd8e3OakAySlK27q0TvJm15Ew8KADWTN"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the API
- [DEBUG] Using autodesk Key=2mm8Rl67MjoMruYfyIBg5#zy2pxcxIQfet
- [DEBUG] Using autodesk Secret=RHGklpa
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/autoklose/autoklose.go b/pkg/detectors/autoklose/autoklose.go
deleted file mode 100644
index e675257afacf..000000000000
--- a/pkg/detectors/autoklose/autoklose.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package autoklose
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"autoklose"}) + `\b([a-zA-Z0-9-]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"autoklose"}
-}
-
-// FromData will find and optionally verify Autoklose secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Autoklose,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, extraData, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
- // API Documentation: https://api.aklab.xyz/#auth-info-fd71acd1-2e41-4991-8789-3edfd258479a
- url := fmt.Sprintf("https://api.autoklose.com/api/me/?api_token=%s", token)
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
- if err != nil {
- return false, nil, err
- }
- req.Header.Add("Accept", "application/json")
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, nil, err
- }
-
- var responseBody map[string]interface{}
- if err := json.Unmarshal(bodyBytes, &responseBody); err != nil {
- return false, nil, err
- }
-
- if email, ok := responseBody["email"].(string); ok {
- return true, map[string]string{"email": email}, nil
- }
- return true, nil, nil
- case http.StatusUnauthorized:
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Autoklose
-}
-
-func (s Scanner) Description() string {
- return "Autoklose is a sales automation tool that allows users to streamline their email outreach and follow-up processes. Autoklose API tokens can be used to access and manage campaigns, contacts, and other related data."
-}
diff --git a/pkg/detectors/autoklose/autoklose_integration_test.go b/pkg/detectors/autoklose/autoklose_integration_test.go
deleted file mode 100644
index 9675d22cba0f..000000000000
--- a/pkg/detectors/autoklose/autoklose_integration_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package autoklose
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAutoklose_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AUTOKLOSE")
- inactiveSecret := testSecrets.MustGetField("AUTOKLOSE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a autoklose secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Autoklose,
- Verified: true,
- ExtraData: map[string]string{
- "email": "mladen.stevanovic@vanillasoft.com",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a autoklose secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Autoklose,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Autoklose.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Autoklose.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/autoklose/autoklose_test.go b/pkg/detectors/autoklose/autoklose_test.go
deleted file mode 100644
index 6553cca913e1..000000000000
--- a/pkg/detectors/autoklose/autoklose_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package autoklose
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAutoKlose_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the autoklose API
- [DEBUG] Using Key=KRXaU9GK3f9yHG1FS-mbwhsIXdW22epH
- [INFO] Response received: 200 OK
- `,
- want: []string{"KRXaU9GK3f9yHG1FS-mbwhsIXdW22epH"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {autoklose}
- {AQAAABAAA Z6Q4KENlmgGJT-M-BLoup9Dmyj2YVC-I}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"Z6Q4KENlmgGJT-M-BLoup9Dmyj2YVC-I"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the autoklose API
- [DEBUG] Using Key=KRXaU9GK3f[yHG1FS$]bwhsIXdW22epH
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/autopilot/autopilot.go b/pkg/detectors/autopilot/autopilot.go
deleted file mode 100644
index 617a71a05d4b..000000000000
--- a/pkg/detectors/autopilot/autopilot.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package autopilot
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"autopilot"}) + `\b([0-9a-f]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"autopilot"}
-}
-
-// FromData will find and optionally verify AutoPilot secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AutoPilot,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api2.autopilothq.com/v1/account", nil)
- if err != nil {
- continue
- }
- req.Header.Add("autopilotapikey", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AutoPilot
-}
-
-func (s Scanner) Description() string {
- return "AutoPilot is a marketing automation platform. AutoPilot API keys can be used to access and manage marketing data and campaigns."
-}
diff --git a/pkg/detectors/autopilot/autopilot_integration_test.go b/pkg/detectors/autopilot/autopilot_integration_test.go
deleted file mode 100644
index b15e26cd8e27..000000000000
--- a/pkg/detectors/autopilot/autopilot_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package autopilot
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAutoPilot_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AUTOPILOT")
- inactiveSecret := testSecrets.MustGetField("AUTOPILOT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a autopilot secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AutoPilot,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a autopilot secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AutoPilot,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AutoPilot.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AutoPilot.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/autopilot/autopilot_test.go b/pkg/detectors/autopilot/autopilot_test.go
deleted file mode 100644
index eee6f18014f5..000000000000
--- a/pkg/detectors/autopilot/autopilot_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package autopilot
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAutoPilot_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the autopilot API
- [DEBUG] Using Key=0fd87cfb1ca6c38c5f1ae5be7b0e395e
- [INFO] Response received: 200 OK
- `,
- want: []string{"0fd87cfb1ca6c38c5f1ae5be7b0e395e"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {autopilot}
- {AQAAABAAA 60aa8204a2b1dec8af7de45737fed7be}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"60aa8204a2b1dec8af7de45737fed7be"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the autopilot API
- [DEBUG] Using Key=KRXaU9GK3f[yHG1FS$]bwhsIXdW22epH
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken.go b/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken.go
deleted file mode 100644
index d9fcbadf8e93..000000000000
--- a/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package avazapersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- // The number prefix increments for every Personal Access Token created.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"avaza"}) + `\b([0-9]+-[0-9a-f]{40})\b`)
-)
-
-// 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{"avaza"}
-}
-
-// FromData will find and optionally verify AvazaPersonalAccessToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AvazaPersonalAccessToken,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- // API Documentation: https://api.avaza.com/swagger/ui/index#!/Account/Account_Get
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.avaza.com/api/Account", http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AvazaPersonalAccessToken
-}
-
-func (s Scanner) Description() string {
- return "Avaza is a business management tool that offers project management, time tracking, and financial management. Avaza Personal Access Tokens can be used to access and interact with Avaza's API."
-}
diff --git a/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken_integration_test.go b/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken_integration_test.go
deleted file mode 100644
index 80664332bfba..000000000000
--- a/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package avazapersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAvazaPersonalAccessToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AVAZAPERSONALACCESSTOKEN")
- inactiveSecret := testSecrets.MustGetField("AVAZAPERSONALACCESSTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a avaza secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AvazaPersonalAccessToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a avaza secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AvazaPersonalAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AvazaPersonalAccessToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AvazaPersonalAccessToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken_test.go b/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken_test.go
deleted file mode 100644
index 2c143c40ad0a..000000000000
--- a/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package avazapersonalaccesstoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAvazaPersonalAccessToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the avaza API
- [DEBUG] Using Key=01818612883613176996369293-f113ceb9cf4fa63dc367ab4815b0e1edf890745f
- [INFO] Response received: 200 OK
- `,
- want: []string{"01818612883613176996369293-f113ceb9cf4fa63dc367ab4815b0e1edf890745f"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {avaza}
- {AQAAABAAA 6605785514902-06e236581be50b798459a53fcb7609032bf813f7}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"6605785514902-06e236581be50b798459a53fcb7609032bf813f7"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the avaza API
- [DEBUG] Using Key=01818612883613176996369293-fzz3ceb0mf4fp63dh367xb4815b0e1edf890745f
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/aviationstack/aviationstack.go b/pkg/detectors/aviationstack/aviationstack.go
deleted file mode 100644
index 8626e9ac17fe..000000000000
--- a/pkg/detectors/aviationstack/aviationstack.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package aviationstack
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"aviationstack"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"aviationstack"}
-}
-
-// FromData will find and optionally verify AviationStack secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AviationStack,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- client.Timeout = 10 * time.Second
- url := fmt.Sprintf("https://api.aviationstack.com/v1/flights?access_key=%s", token)
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
- if err != nil {
- return false, err
- }
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AviationStack
-}
-
-func (s Scanner) Description() string {
- return "AviationStack is a service providing real-time flight status and aviation data. The API key can be used to access this data."
-}
diff --git a/pkg/detectors/aviationstack/aviationstack_integration_test.go b/pkg/detectors/aviationstack/aviationstack_integration_test.go
deleted file mode 100644
index b716fcd195e7..000000000000
--- a/pkg/detectors/aviationstack/aviationstack_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package aviationstack
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAviationStack_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AVIATIONSTACK")
- inactiveSecret := testSecrets.MustGetField("AVIATIONSTACK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aviationstack secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AviationStack,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aviationstack secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AviationStack,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AviationStack.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("AviationStack.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/aviationstack/aviationstack_test.go b/pkg/detectors/aviationstack/aviationstack_test.go
deleted file mode 100644
index 0a1dbdea2c13..000000000000
--- a/pkg/detectors/aviationstack/aviationstack_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package aviationstack
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAviationStack_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the aviationstack API
- [DEBUG] Using Key=osh0kjinsc2atoaqntoy1hdjppg54449
- [INFO] Response received: 200 OK
- `,
- want: []string{"osh0kjinsc2atoaqntoy1hdjppg54449"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {aviationstack}
- {AQAAABAAA 464r3ib5xzipgd36zdzpvm09p00juu0b}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"464r3ib5xzipgd36zdzpvm09p00juu0b"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the aviationstack API
- [DEBUG] Using Key=OSh0lMjinsc2atoaqnto[]1hdjppg5449
- [ERROR] Response received: 400 BadRequest
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/aws/access_keys/accesskey.go b/pkg/detectors/aws/access_keys/accesskey.go
deleted file mode 100644
index 99eb2fe73312..000000000000
--- a/pkg/detectors/aws/access_keys/accesskey.go
+++ /dev/null
@@ -1,335 +0,0 @@
-package access_keys
-
-import (
- "context"
- "fmt"
- "net"
- "net/http"
- "strings"
- "time"
-
- awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
- "github.com/aws/aws-sdk-go-v2/config"
- "github.com/aws/aws-sdk-go-v2/credentials"
- "github.com/aws/aws-sdk-go-v2/service/sts"
- "github.com/aws/smithy-go/middleware"
- smithyhttp "github.com/aws/smithy-go/transport/http"
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aws"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type scanner struct {
- verificationClient config.HTTPClient
- skipIDs map[string]struct{}
- detectors.AccountFilter
- detectors.DefaultMultiPartCredentialProvider
-}
-
-func New(opts ...func(*scanner)) *scanner {
- scanner := &scanner{
- skipIDs: map[string]struct{}{},
- }
- for _, opt := range opts {
- opt(scanner)
- }
-
- return scanner
-}
-
-func WithSkipIDs(skipIDs []string) func(*scanner) {
- return func(s *scanner) {
- ids := map[string]struct{}{}
- for _, id := range skipIDs {
- ids[id] = struct{}{}
- }
-
- s.skipIDs = ids
- }
-}
-
-func WithAllowedAccounts(accounts []string) func(*scanner) {
- return func(s *scanner) {
- s.SetAllowedAccounts(accounts)
- }
-}
-
-func WithDeniedAccounts(accounts []string) func(*scanner) {
- return func(s *scanner) {
- s.SetDeniedAccounts(accounts)
- }
-}
-
-// Ensure the scanner satisfies the interface at compile time.
-var _ interface {
- detectors.Detector
- detectors.CustomResultsCleaner
- detectors.MultiPartCredentialProvider
-} = (*scanner)(nil)
-
-var (
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- // Key types are from this list https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids
- idPat = regexp.MustCompile(`\b((?:AKIA|ABIA|ACCA)[A-Z0-9]{16})\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{
- "AKIA",
- "ABIA",
- "ACCA",
- }
-}
-
-// The recommended way by AWS is to use the SDK's http client.
-// https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-http.html
-// Note: Using default http.Client causes SignatureInvalid error in response. therefore, based on http default client implementation, we are using the same configuration.
-func getDefaultBuildableClient() *awshttp.BuildableClient {
- return awshttp.NewBuildableClient().
- WithTimeout(common.DefaultResponseTimeout).
- WithDialerOptions(func(dialer *net.Dialer) {
- dialer.Timeout = 2 * time.Second
- dialer.KeepAlive = 5 * time.Second
- }).
- WithTransportOptions(func(tr *http.Transport) {
- tr.Proxy = http.ProxyFromEnvironment
- tr.MaxIdleConns = 5
- tr.IdleConnTimeout = 5 * time.Second
- tr.TLSHandshakeTimeout = 3 * time.Second
- tr.ExpectContinueTimeout = 1 * time.Second
- })
-}
-
-func (s scanner) getAWSBuilableClient() config.HTTPClient {
- if s.verificationClient == nil {
- s.verificationClient = getDefaultBuildableClient()
- }
- return s.verificationClient
-}
-
-// FromData will find and optionally verify AWS secrets in a given set of bytes.
-func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- logger := logContext.AddLogger(ctx).Logger().WithName("aws")
- dataStr := string(data)
- dataStr = aws.UrlEncodedReplacer.Replace(dataStr)
-
- // Filter & deduplicate matches.
- idMatches := make(map[string]struct{})
- for _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {
- idMatches[matches[1]] = struct{}{}
- }
- secretMatches := make(map[string]struct{})
- for _, matches := range aws.SecretPat.FindAllStringSubmatch(dataStr, -1) {
- secretMatches[matches[1]] = struct{}{}
- }
-
- // Process matches.
- for idMatch := range idMatches {
- if detectors.StringShannonEntropy(idMatch) < aws.RequiredIdEntropy {
- continue
- }
- if s.skipIDs != nil {
- if _, ok := s.skipIDs[idMatch]; ok {
- continue
- }
- }
-
- for secretMatch := range secretMatches {
- if detectors.StringShannonEntropy(secretMatch) < aws.RequiredSecretEntropy {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AWS,
- Raw: []byte(idMatch),
- Redacted: idMatch,
- RawV2: []byte(idMatch + ":" + secretMatch),
- ExtraData: map[string]string{
- "resource_type": aws.ResourceTypes[idMatch[:4]],
- },
- }
-
- // Decode the AWS Account ID.
- accountID, err := aws.GetAccountNumFromID(idMatch)
- isCanary := false
- if err != nil {
- logger.V(3).Info("Failed to decode AWS Account ID", "err", err)
- } else {
- s1.ExtraData["account"] = accountID
-
- // Check if this is a canary token
- if _, ok := thinkstCanaryList[accountID]; ok {
- isCanary = true
- s1.ExtraData["message"] = thinkstMessage
- }
- if _, ok := thinkstKnockoffsCanaryList[accountID]; ok {
- isCanary = true
- s1.ExtraData["message"] = thinkstKnockoffsMessage
- }
-
- if isCanary {
- s1.ExtraData["is_canary"] = "true"
- }
- }
-
- if verify {
- // Check account filtering before verification for ALL secrets (including canaries)
- if accountID != "" {
- if s.ShouldSkipAccount(accountID) {
- var skipReason string
- if s.IsInDenyList(accountID) {
- skipReason = aws.VerificationErrAccountIDInDenyList
- } else {
- skipReason = aws.VerificationErrAccountIDNotInAllowList
- }
- s1.SetVerificationError(fmt.Errorf("%s", skipReason), secretMatch)
- results = append(results, s1)
- continue
- }
- }
-
- // Perform verification based on token type
- if isCanary {
- // Canary verification logic
- verified, arn, err := s.verifyCanary(ctx, idMatch, secretMatch)
- s1.Verified = verified
- if arn != "" {
- s1.ExtraData["arn"] = arn
- }
- s1.SetVerificationError(err, secretMatch)
- } else {
- // Normal verification logic
- isVerified, extraData, verificationErr := s.verifyMatch(ctx, idMatch, secretMatch, len(secretMatches) > 1)
- s1.Verified = isVerified
-
- // Log if the calculated ID does not match the ID value from verification.
- // Should only be edge cases at most.
- if accountID != "" && extraData["account"] != "" && extraData["account"] != s1.ExtraData["account"] {
- logger.V(2).Info("Calculated account ID does not match actual account ID", "calculated", accountID, "actual", extraData["account"])
- }
-
- // Append the extraData to the existing ExtraData map.
- for k, v := range extraData {
- s1.ExtraData[k] = v
- }
- s1.SetVerificationError(verificationErr, secretMatch)
- }
- }
-
- if !s1.Verified && aws.FalsePositiveSecretPat.MatchString(secretMatch) {
- // Unverified results that look like hashes are probably not secrets
- continue
- }
-
- results = append(results, s1)
- // If we've found a verified match with this ID, we don't need to look for any more. So move on to the next ID.
- if s1.Verified {
- delete(secretMatches, secretMatch)
- break
- }
- }
- }
- return results, nil
-}
-
-func (s scanner) ShouldCleanResultsIrrespectiveOfConfiguration() bool {
- return true
-}
-
-const (
- method = "GET"
- service = "sts"
- host = "sts.amazonaws.com"
- region = "us-east-1"
- endpoint = "https://sts.amazonaws.com"
-)
-
-func (s scanner) verifyMatch(ctx context.Context, resIDMatch, resSecretMatch string, retryOn403 bool) (bool, map[string]string, error) {
- // Prep AWS Creds for STS
- cfg, err := config.LoadDefaultConfig(ctx,
- config.WithRegion(region),
- config.WithHTTPClient(s.getAWSBuilableClient()),
- config.WithCredentialsProvider(
- credentials.NewStaticCredentialsProvider(resIDMatch, resSecretMatch, ""),
- ),
- )
- if err != nil {
- return false, nil, err
- }
- // Create STS client
- stsClient := sts.NewFromConfig(cfg, func(o *sts.Options) {
- o.APIOptions = append(o.APIOptions, replaceUserAgentMiddleware)
- })
-
- // Make the GetCallerIdentity API call
- resp, err := stsClient.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
- if err != nil {
- // Experimentation has indicated that if you make multiple GetCallerIdentity requests within five seconds that
- // share a key ID but are signed with different secrets the second one will be rejected with a 403 that
- // carries a SignatureDoesNotMatch code in its body. This happens even if the second ID-secret pair is
- // valid. Since this is exactly our access pattern, we need to work around it.
- //
- // Fortunately, experimentation has also revealed a workaround: simply resubmit the second request. The
- // response to the resubmission will be as expected.
- //
- // We are clearly deep in the guts of AWS implementation details here, so this all might change with no
- // notice. If you're here because something in this detector broke, you have my condolences.
- if strings.Contains(err.Error(), "StatusCode: 403") {
- if retryOn403 {
- return s.verifyMatch(ctx, resIDMatch, resSecretMatch, false)
- }
- return false, nil, nil
- } else if strings.Contains(err.Error(), "InvalidClientTokenId") {
- return false, nil, nil
- }
- return false, nil, fmt.Errorf("request returned unexpected error: %w", err)
- }
-
- extraData := map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/",
- "account": *resp.Account,
- "user_id": *resp.UserId,
- "arn": *resp.Arn,
- }
- return true, extraData, nil
-}
-
-func (s scanner) CleanResults(results []detectors.Result) []detectors.Result {
- return aws.CleanResults(results)
-}
-
-func (s scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AWS
-}
-
-func (s scanner) Description() string {
- return "AWS (Amazon Web Services) is a comprehensive cloud computing platform offering a wide range of on-demand services like computing power, storage, databases. API keys for AWS can have varying amount of access to these services depending on the IAM policy attached."
-}
-
-// Adds a custom Build middleware to the stack to replace the User-Agent header of the final request
-// https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/middleware.html
-func replaceUserAgentMiddleware(stack *middleware.Stack) error {
- return stack.Build.Add(
- middleware.BuildMiddlewareFunc(
- "ReplaceUserAgent",
- func(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (
- out middleware.BuildOutput, metadata middleware.Metadata, err error,
- ) {
- req, ok := in.Request.(*smithyhttp.Request)
- if !ok {
- return next.HandleBuild(ctx, in)
- }
- req.Header.Set("User-Agent", common.UserAgent())
- return next.HandleBuild(ctx, in)
- },
- ),
- middleware.After,
- )
-}
diff --git a/pkg/detectors/aws/access_keys/accesskey_integration_test.go b/pkg/detectors/aws/access_keys/accesskey_integration_test.go
deleted file mode 100644
index ef01ddad9a6e..000000000000
--- a/pkg/detectors/aws/access_keys/accesskey_integration_test.go
+++ /dev/null
@@ -1,458 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package access_keys
-
-import (
- "context"
- "fmt"
- "sort"
- "testing"
- "time"
-
- "github.com/brianvoe/gofakeit/v7"
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/stretchr/testify/assert"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-const canaryAccessKeyID = "AKIASP2TPHJSQH3FJRUX"
-
-var unverifiedSecretClient = common.ConstantResponseHttpClient(403, `{"Error": {"Code": "InvalidClientTokenId"} }`)
-
-// Our AWS detector interacts with AWS in an (expectedly) uncommon way that triggers some odd AWS behavior. (This odd
-// behavior doesn't affect "normal" AWS use, so it's not really "broken" - it's just something that we have to work
-// around.) The AWS detector code has a long comment explaining this in more detail, but the basic issue is that AWS STS
-// is stateful, so the behavior of these tests can vary depending on which of them you run, and in which order. This
-// particular test (TestAWS_FromChunk_InvalidValidReuseIDSequence) duplicates some logic in the "big" test table in the
-// other test in this file, but extracting it in this way as well makes it fail more consistently when it's supposed to
-// fail, which is why it's extracted.
-func TestAWS_FromChunk_InvalidValidReuseIDSequence(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AWS")
- id := testSecrets.MustGetField("AWS_ID")
- inactiveSecret := testSecrets.MustGetField("AWS_INACTIVE")
-
- d := scanner{}
-
- ignoreOpts := []cmp.Option{cmpopts.IgnoreFields(detectors.Result{}, "RawV2", "Raw", "verificationError")}
-
- got, err := d.FromData(ctx, true, []byte(fmt.Sprintf("aws %s %s", id, inactiveSecret)))
- if assert.NoError(t, err) {
- want := []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: false,
- Redacted: "AKIAZAVB57H55F3T4BKH",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- },
- },
- }
- if diff := cmp.Diff(got, want, ignoreOpts...); diff != "" {
- t.Errorf("AWS.FromData() (valid ID, invalid secret) diff: (-got +want)\n%s", diff)
- }
- }
-
- got, err = d.FromData(ctx, true, []byte(fmt.Sprintf("aws %s %s", id, secret)))
- if assert.NoError(t, err) {
- want := []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: true,
- Redacted: "AKIAZAVB57H55F3T4BKH",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- "arn": "arn:aws:iam::619888638459:user/trufflehog-aws-detector-tester",
- "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/",
- "user_id": "AIDAZAVB57H5V3Q4ACRGM",
- },
- },
- }
- if diff := cmp.Diff(got, want, ignoreOpts...); diff != "" {
- t.Errorf("AWS.FromData() (valid secret after invalid secret using same ID) diff: (-got +want)\n%s", diff)
- }
- }
-}
-
-func TestAWS_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AWS")
- id := testSecrets.MustGetField("AWS_ID")
- inactiveSecret := testSecrets.MustGetField("AWS_INACTIVE")
- inactiveID := id[:len(id)-3] + "XYZ"
-
- hash := gofakeit.Password(true, true, true, false, false, 10)
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationError bool
- }{
- {
- name: "found, verified",
- s: scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: true,
- Redacted: "AKIAZAVB57H55F3T4BKH",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- "arn": "arn:aws:iam::619888638459:user/trufflehog-aws-detector-tester",
- "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/",
- "user_id": "AIDAZAVB57H5V3Q4ACRGM",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: scanner{verificationClient: unverifiedSecretClient},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: false,
- Redacted: "AKIAZAVB57H55F3T4BKH",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- {
- name: "found two, one included for every ID found",
- s: scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("The verified ID is %s with a secret of %s, but the unverified ID is %s and this is the secret %s", id, secret, inactiveID, inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: false,
- Redacted: "AKIAZAVB57H55F3T4XYZ",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- },
- },
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: true,
- Redacted: "AKIAZAVB57H55F3T4BKH",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- "arn": "arn:aws:iam::619888638459:user/trufflehog-aws-detector-tester",
- "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/",
- "user_id": "AIDAZAVB57H5V3Q4ACRGM",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "not found, because unverified secret was a hash",
- s: scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", hash, id)), // The secret would satisfy the regex but be filtered out after not passing validation.
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- {
- name: "found two, returned both because the active secret for one paired with the inactive ID, despite the hash",
- s: scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("The verified ID is %s with a secret of %s, but the unverified ID is %s and the secret is this hash %s", id, secret, inactiveID, hash)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: true,
- Redacted: "AKIAZAVB57H55F3T4BKH",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- "arn": "arn:aws:iam::619888638459:user/trufflehog-aws-detector-tester",
- "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/",
- "user_id": "AIDAZAVB57H5V3Q4ACRGM",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified, with leading +",
- s: scanner{
- verificationClient: unverifiedSecretClient,
- },
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", "+HaNv9cTwheDKGJaws/+BMF2GgybQgBWdhcOOdfF", id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: false,
- Redacted: "AKIAZAVB57H55F3T4BKH",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "skipped",
- s: scanner{
- skipIDs: map[string]struct{}{
- "AKIAZAVB57H55F3T4BKH": {},
- },
- },
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", "+HaNv9cTwheDKGJaws/+BMF2GgybQgBWdhcOOdfF", id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- wantErr: false,
- },
- {
- name: "found, would be verified if not for http timeout",
- s: scanner{
- verificationClient: common.SaneHttpClientTimeOut(1 * time.Microsecond),
- },
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: false,
- Redacted: "AKIAZAVB57H55F3T4BKH",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- },
- },
- },
- wantErr: false,
- wantVerificationError: true,
- },
- {
- name: "found, unverified due to unexpected http response status",
- s: scanner{
- verificationClient: common.ConstantResponseHttpClient(500, "internal server error"),
- },
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: false,
- Redacted: "AKIAZAVB57H55F3T4BKH",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- },
- },
- },
- wantErr: false,
- wantVerificationError: true,
- },
- {
- name: "found, unverified due to invalid aws_secret with valid canary access_key_id",
- s: scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", inactiveSecret, canaryAccessKeyID)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: false,
- Redacted: canaryAccessKeyID,
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "171436882533",
- "is_canary": "true",
- "message": "This is an AWS canary token generated at canarytokens.org, and was not set off; learn more here: https://trufflesecurity.com/canaries",
- },
- },
- },
- wantErr: false,
- wantVerificationError: false,
- },
- {
- name: "found, valid canary token with no verification",
- s: scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, canaryAccessKeyID)),
- verify: false,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: false,
- Redacted: canaryAccessKeyID,
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "171436882533",
- "is_canary": "true",
- "message": "This is an AWS canary token generated at canarytokens.org, and was not set off; learn more here: https://trufflesecurity.com/canaries",
- },
- },
- },
- wantErr: false,
- wantVerificationError: false,
- },
- {
- name: "verified secret checked directly after unverified secret with same key id",
- s: scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("%s\n%s\n%s", inactiveSecret, id, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: false,
- Redacted: "AKIAZAVB57H55F3T4BKH",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- },
- },
- {
- DetectorType: detectorspb.DetectorType_AWS,
- Verified: true,
- Redacted: "AKIAZAVB57H55F3T4BKH",
- ExtraData: map[string]string{
- "resource_type": "Access key",
- "account": "619888638459",
- "arn": "arn:aws:iam::619888638459:user/trufflehog-aws-detector-tester",
- "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/",
- "user_id": "AIDAZAVB57H5V3Q4ACRGM",
- },
- },
- },
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := tt.s
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AWS.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationError {
- t.Fatalf("wantVerificationError %v, verification error = %v", tt.wantVerificationError, got[i].VerificationError())
- }
- }
- ignoreOpts := []cmp.Option{
- cmpopts.IgnoreFields(detectors.Result{}, "RawV2", "Raw", "verificationError"),
- cmpopts.SortSlices(func(x, y detectors.Result) bool {
- return x.Redacted < y.Redacted
- }),
- }
-
- sortResults(tt.want)
- if diff := cmp.Diff(got, tt.want, ignoreOpts...); diff != "" {
- t.Errorf("AWS.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-// Helper function to sort results due to the order of the redacted
-func sortResults(results []detectors.Result) {
- sort.SliceStable(results, func(i, j int) bool {
- return results[i].Redacted < results[j].Redacted
- })
-}
-
-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)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/aws/access_keys/accesskey_test.go b/pkg/detectors/aws/access_keys/accesskey_test.go
deleted file mode 100644
index bf6cb4774106..000000000000
--- a/pkg/detectors/aws/access_keys/accesskey_test.go
+++ /dev/null
@@ -1,205 +0,0 @@
-package access_keys
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAWS_Pattern(t *testing.T) {
- d := scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- aws credentials{
- id: ABIAS9L8MS5IPHTZPPUQ
- secret: .v2QPKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63;
- }
- `,
- want: []string{"ABIAS9L8MS5IPHTZPPUQ:v2QPKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {AKIAWGXZ9OPDOWUJMZGI}
- {AQAAABAAA .v2QPKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63;}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"AKIAWGXZ9OPDOWUJMZGI:v2QPKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63"},
- },
- {
- name: "invalid pattern",
- input: `
- aws credentials{
- id: AKIAs9L8MS5iPHTZPPUQ
- secret: $YenOG.PKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63;
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
-
-func TestAWS_WithAllowedAccounts(t *testing.T) {
- accounts := []string{"123456789012", "999888777666"}
- s := New(WithAllowedAccounts(accounts))
-
- // Test that allowed accounts are properly configured
- shouldSkip := s.ShouldSkipAccount("123456789012")
- require.False(t, shouldSkip)
- require.True(t, s.IsInAllowList("123456789012"))
-
- // Test that non-allowed accounts are skipped
- shouldSkip = s.ShouldSkipAccount("111222333444")
- require.True(t, shouldSkip)
- require.False(t, s.IsInAllowList("111222333444"))
-}
-
-func TestAWS_WithDeniedAccounts(t *testing.T) {
- accounts := []string{"123456789012", "999888777666"}
- s := New(WithDeniedAccounts(accounts))
-
- // Test that denied accounts are properly skipped
- shouldSkip := s.ShouldSkipAccount("123456789012")
- require.True(t, shouldSkip)
- require.True(t, s.IsInDenyList("123456789012"))
-
- // Test that non-denied accounts are not skipped
- shouldSkip = s.ShouldSkipAccount("111222333444")
- require.False(t, shouldSkip)
- require.False(t, s.IsInDenyList("111222333444"))
-}
-
-func TestAWS_CanaryTokenFiltering(t *testing.T) {
- // Using known canary token from integration tests
- canaryAccessKeyID := "AKIASP2TPHJSQH3FJRUX" // Account ID: 171436882533
- canarySecret := "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
- testData := []byte(fmt.Sprintf("%s:%s", canaryAccessKeyID, canarySecret))
-
- t.Run("debug canary detection", func(t *testing.T) {
- // First, let's test basic canary detection without verification
- s := New()
-
- results, err := s.FromData(context.Background(), false, testData) // verify = false
- require.NoError(t, err)
- require.Len(t, results, 1)
-
- result := results[0]
- t.Logf("Result without verification - Verified: %v, Account: %s, IsCanary: %s, Message: %s",
- result.Verified, result.ExtraData["account"], result.ExtraData["is_canary"], result.ExtraData["message"])
-
- // Should detect as canary but not verify (since verify=false)
- require.False(t, result.Verified)
- require.Equal(t, "171436882533", result.ExtraData["account"])
- require.Equal(t, "true", result.ExtraData["is_canary"])
- require.Contains(t, result.ExtraData["message"], "canarytokens.org")
- })
-
- t.Run("canary token with allow list - account not allowed", func(t *testing.T) {
- // Configure scanner with allow list that excludes the canary account
- s := New(WithAllowedAccounts([]string{"123456789012", "999888777666"}))
-
- results, err := s.FromData(context.Background(), true, testData)
- require.NoError(t, err)
- require.Len(t, results, 1)
-
- result := results[0]
- // Should detect the canary token but not verify it due to filtering
- require.False(t, result.Verified)
- require.NotNil(t, result.VerificationError())
- require.Contains(t, result.VerificationError().Error(), "not in the allow list")
- require.Equal(t, "171436882533", result.ExtraData["account"])
- require.Equal(t, "true", result.ExtraData["is_canary"])
- })
-
- t.Run("canary token with deny list - account denied", func(t *testing.T) {
- // Configure scanner with deny list that includes the canary account
- s := New(WithDeniedAccounts([]string{"171436882533", "123456789012"}))
-
- results, err := s.FromData(context.Background(), true, testData)
- require.NoError(t, err)
- require.Len(t, results, 1)
-
- result := results[0]
- // Should detect the canary token but not verify it due to filtering
- require.False(t, result.Verified)
- require.NotNil(t, result.VerificationError())
- require.Contains(t, result.VerificationError().Error(), "in the deny list")
- require.Equal(t, "171436882533", result.ExtraData["account"])
- require.Equal(t, "true", result.ExtraData["is_canary"])
- })
-
- t.Run("precedence test - deny list takes precedence over allow list", func(t *testing.T) {
- // Configure scanner where canary account is in both allow and deny lists
- s := New(
- WithAllowedAccounts([]string{"171436882533", "123456789012"}),
- WithDeniedAccounts([]string{"171436882533"}),
- )
-
- results, err := s.FromData(context.Background(), true, testData)
- require.NoError(t, err)
- require.Len(t, results, 1)
-
- result := results[0]
- // Should detect the canary token but not verify it since deny takes precedence
- require.False(t, result.Verified)
- require.NotNil(t, result.VerificationError())
- require.Contains(t, result.VerificationError().Error(), "in the deny list")
- require.Equal(t, "171436882533", result.ExtraData["account"])
- require.Equal(t, "true", result.ExtraData["is_canary"])
- })
-}
diff --git a/pkg/detectors/aws/access_keys/canary.go b/pkg/detectors/aws/access_keys/canary.go
deleted file mode 100644
index 3bf6273bd752..000000000000
--- a/pkg/detectors/aws/access_keys/canary.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package access_keys
-
-import (
- "context"
- "strings"
-
- "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/config"
- "github.com/aws/aws-sdk-go-v2/credentials"
- "github.com/aws/aws-sdk-go-v2/service/sns"
-)
-
-const thinkstMessage = "This is an AWS canary token generated at canarytokens.org."
-const thinkstKnockoffsMessage = "This is an off brand AWS Canary inspired by canarytokens.org."
-
-var (
- thinkstCanaryList = map[string]struct{}{
- "052310077262": {},
- "171436882533": {},
- "534261010715": {},
- "595918472158": {},
- "717712589309": {},
- "819147034852": {},
- "992382622183": {},
- "730335385048": {},
- "266735846894": {},
- "893192397702": {},
- }
- thinkstKnockoffsCanaryList = map[string]struct{}{
- "044858866125": {},
- "251535659677": {},
- "344043088457": {},
- "351906852752": {},
- "390477818340": {},
- "426127672474": {},
- "427150556519": {},
- "439872796651": {},
- "445142720921": {},
- "465867158099": {},
- "637958123769": {},
- "693412236332": {},
- "732624840810": {},
- "735421457923": {},
- "959235150393": {},
- "982842642351": {},
- }
-)
-
-func (s scanner) verifyCanary(ctx context.Context, resIDMatch, resSecretMatch string) (bool, string, error) {
- // Prep AWS Creds for SNS
- cfg, err := config.LoadDefaultConfig(ctx,
- config.WithRegion(region),
- config.WithHTTPClient(s.getAWSBuilableClient()),
- config.WithCredentialsProvider(
- credentials.NewStaticCredentialsProvider(resIDMatch, resSecretMatch, ""),
- ),
- )
- if err != nil {
- return false, "", err
- }
- svc := sns.NewFromConfig(cfg, func(o *sns.Options) {
- o.APIOptions = append(o.APIOptions, replaceUserAgentMiddleware)
- })
-
- // Prep vars and Publish to SNS
- _, err = svc.Publish(ctx, &sns.PublishInput{
- Message: aws.String("foo"),
- PhoneNumber: aws.String("1"),
- })
-
- if strings.Contains(err.Error(), "not authorized to perform") {
- arn := strings.Split(err.Error(), "User: ")[1]
- arn = strings.Split(arn, " is not authorized to perform: ")[0]
- return true, arn, nil
- } else if strings.Contains(err.Error(), "does not match the signature you provided") {
- return false, "", nil
- } else if strings.Contains(err.Error(), "status code: 403") || strings.Contains(err.Error(), "InvalidClientTokenId") {
- return false, "", nil
- } else {
- return false, "", err
- }
-}
diff --git a/pkg/detectors/aws/common.go b/pkg/detectors/aws/common.go
deleted file mode 100644
index 02bf5a713054..000000000000
--- a/pkg/detectors/aws/common.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package aws
-
-import regexp "github.com/wasilibs/go-re2"
-
-const (
- RequiredIdEntropy = 3.0
- RequiredSecretEntropy = 4.25
-)
-
-// Verification error messages
-const (
- VerificationErrAccountIDInDenyList = "Account ID is in the deny list for verification"
- VerificationErrAccountIDNotInAllowList = "Account ID is not in the allow list for verification"
-)
-
-var SecretPat = regexp.MustCompile(`(?:[^A-Za-z0-9+/]|\A)([A-Za-z0-9+/]{40})(?:[^A-Za-z0-9+/]|\z)`)
-
-type IdentityResponse struct {
- GetCallerIdentityResponse struct {
- GetCallerIdentityResult struct {
- Account string `json:"Account"`
- Arn string `json:"Arn"`
- UserID string `json:"UserId"`
- } `json:"GetCallerIdentityResult"`
- ResponseMetadata struct {
- RequestID string `json:"RequestId"`
- } `json:"ResponseMetadata"`
- } `json:"GetCallerIdentityResponse"`
-}
-
-type Error struct {
- Code string `json:"Code"`
- Message string `json:"Message"`
-}
-
-type ErrorResponseBody struct {
- Error Error `json:"Error"`
-}
diff --git a/pkg/detectors/aws/session_keys/sessionkey.go b/pkg/detectors/aws/session_keys/sessionkey.go
deleted file mode 100644
index 7017478371b0..000000000000
--- a/pkg/detectors/aws/session_keys/sessionkey.go
+++ /dev/null
@@ -1,348 +0,0 @@
-package session_keys
-
-import (
- "context"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aws"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type scanner struct {
- *detectors.CustomMultiPartCredentialProvider
- verificationClient *http.Client
- skipIDs map[string]struct{}
- detectors.AccountFilter
-}
-
-func New(opts ...func(*scanner)) *scanner {
- scanner := &scanner{
- skipIDs: map[string]struct{}{},
- }
- for _, opt := range opts {
- opt(scanner)
- }
-
- scanner.CustomMultiPartCredentialProvider = detectors.NewCustomMultiPartCredentialProvider(2048)
- return scanner
-}
-
-func WithSkipIDs(skipIDs []string) func(*scanner) {
- return func(s *scanner) {
- ids := map[string]struct{}{}
- for _, id := range skipIDs {
- ids[id] = struct{}{}
- }
-
- s.skipIDs = ids
- }
-}
-
-func WithAllowedAccounts(accounts []string) func(*scanner) {
- return func(s *scanner) {
- s.SetAllowedAccounts(accounts)
- }
-}
-
-func WithDeniedAccounts(accounts []string) func(*scanner) {
- return func(s *scanner) {
- s.SetDeniedAccounts(accounts)
- }
-}
-
-// Ensure the scanner satisfies the interface at compile time.
-var _ interface {
- detectors.Detector
- detectors.CustomResultsCleaner
-} = (*scanner)(nil)
-
-var (
- defaultVerificationClient = common.SaneHttpClient()
-
- // Key types are from this list https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids
- idPat = regexp.MustCompile(`\b((?:ASIA)[A-Z0-9]{16})\b`)
- sessionPat = regexp.MustCompile(`(?:[^A-Za-z0-9+/]|\A)([a-zA-Z0-9+/]{100,}={0,3})(?:[^A-Za-z0-9+/=]|\z)`)
-)
-
-// 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{"ASIA"}
-}
-
-// FromData will find and optionally verify AWS secrets in a given set of bytes.
-func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- logger := logContext.AddLogger(ctx).Logger().WithName("awssessionkey")
- dataStr := string(data)
- dataStr = aws.UrlEncodedReplacer.Replace(dataStr)
-
- // Filter & deduplicate matches.
- idMatches := make(map[string]struct{})
- for _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {
- idMatches[matches[1]] = struct{}{}
- }
- secretMatches := make(map[string]struct{})
- for _, matches := range aws.SecretPat.FindAllStringSubmatch(dataStr, -1) {
- secretMatches[matches[1]] = struct{}{}
- }
- sessionMatches := make(map[string]struct{})
- for _, matches := range sessionPat.FindAllStringSubmatch(dataStr, -1) {
- sessionMatches[matches[1]] = struct{}{}
- }
-
- // Process matches.
- for idMatch := range idMatches {
- if detectors.StringShannonEntropy(idMatch) < aws.RequiredIdEntropy {
- continue
- }
- if s.skipIDs != nil {
- if _, ok := s.skipIDs[idMatch]; ok {
- continue
- }
- }
-
- for secretMatch := range secretMatches {
- if detectors.StringShannonEntropy(secretMatch) < aws.RequiredSecretEntropy {
- continue
- }
-
- for sessionMatch := range sessionMatches {
- if detectors.StringShannonEntropy(sessionMatch) < 4.5 {
- continue
- }
- if !checkSessionToken(sessionMatch, secretMatch) {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AWSSessionKey,
- Raw: []byte(idMatch),
- RawV2: []byte(fmt.Sprintf("%s:%s:%s", idMatch, secretMatch, sessionMatch)),
- Redacted: idMatch,
- ExtraData: make(map[string]string),
- }
-
- if verify {
- // If we haven't already found an AWS Account ID for this ID (via API), calculate one for filtering.
- var accountIDForFiltering string
- if accountID, err := aws.GetAccountNumFromID(idMatch); err == nil {
- accountIDForFiltering = accountID
- }
-
- // Check account filtering before verification
- if accountIDForFiltering != "" {
- if s.ShouldSkipAccount(accountIDForFiltering) {
- var skipReason string
- if s.IsInDenyList(accountIDForFiltering) {
- skipReason = aws.VerificationErrAccountIDInDenyList
- } else {
- skipReason = aws.VerificationErrAccountIDNotInAllowList
- }
- s1.SetVerificationError(fmt.Errorf("%s", skipReason), secretMatch)
- // If we haven't already found an AWS Account ID for this ID (via API), calculate one.
- if _, ok := s1.ExtraData["account"]; !ok {
- if accountID, err := aws.GetAccountNumFromID(idMatch); err != nil {
- logger.V(3).Info("Failed to decode AWS Account ID", "err", err)
- } else {
- s1.ExtraData["account"] = accountID
- }
- }
- results = append(results, s1)
- continue
- }
- }
-
- isVerified, extraData, verificationErr := s.verifyMatch(ctx, idMatch, secretMatch, sessionMatch, true)
- s1.Verified = isVerified
- if extraData != nil {
- s1.ExtraData = extraData
- }
- s1.SetVerificationError(verificationErr, secretMatch)
- }
-
- if !s1.Verified && aws.FalsePositiveSecretPat.MatchString(secretMatch) {
- // Unverified results that look like hashes are probably not secrets
- continue
- }
-
- // If we haven't already found an AWS Account ID for this ID (via API), calculate one.
- if _, ok := s1.ExtraData["account"]; !ok {
- if accountID, err := aws.GetAccountNumFromID(idMatch); err != nil {
- logger.V(3).Info("Failed to decode AWS Account ID", "err", err)
- } else {
- s1.ExtraData["account"] = accountID
- }
- }
-
- results = append(results, s1)
- // If we've found a verified match with this ID, we don't need to look for any more. So move on to the next ID.
- if s1.Verified {
- delete(sessionMatches, secretMatch)
- delete(sessionMatches, sessionMatch)
- break
- }
- }
- }
- }
- return results, nil
-}
-
-func (s scanner) ShouldCleanResultsIrrespectiveOfConfiguration() bool {
- return true
-}
-
-const (
- method = "GET"
- service = "sts"
- host = "sts.amazonaws.com"
- region = "us-east-1"
- endpoint = "https://sts.amazonaws.com"
-)
-
-func (s scanner) verifyMatch(ctx context.Context, resIDMatch, resSecretMatch string, resSessionMatch string, retryOn403 bool) (bool, map[string]string, error) {
- // REQUEST VALUES.
- now := time.Now().UTC()
- datestamp := now.Format("20060102")
- amzDate := now.Format("20060102T150405Z")
-
- req, err := http.NewRequestWithContext(ctx, method, endpoint, nil)
- if err != nil {
- return false, nil, err
- }
- req.Header.Set("Accept", "application/json")
-
- // TASK 1: CREATE A CANONICAL REQUEST.
- // http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
- canonicalURI := "/"
- canonicalHeaders := "host:" + host + "\n" + "x-amz-date:" + amzDate + "\n" + "x-amz-security-token:" + resSessionMatch + "\n"
- signedHeaders := "host;x-amz-date;x-amz-security-token"
- algorithm := "AWS4-HMAC-SHA256"
- credentialScope := fmt.Sprintf("%s/%s/%s/aws4_request", datestamp, region, service)
-
- params := req.URL.Query()
- params.Add("Action", "GetCallerIdentity")
- params.Add("Version", "2011-06-15")
- canonicalQuerystring := params.Encode()
- payloadHash := aws.GetHash("") // empty payload
- canonicalRequest := method + "\n" + canonicalURI + "\n" + canonicalQuerystring + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash
-
- // TASK 2: CREATE THE STRING TO SIGN.
- stringToSign := algorithm + "\n" + amzDate + "\n" + credentialScope + "\n" + aws.GetHash(canonicalRequest)
-
- // TASK 3: CALCULATE THE SIGNATURE.
- // https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
- hash := aws.GetHMAC([]byte(fmt.Sprintf("AWS4%s", resSecretMatch)), []byte(datestamp))
- hash = aws.GetHMAC(hash, []byte(region))
- hash = aws.GetHMAC(hash, []byte(service))
- hash = aws.GetHMAC(hash, []byte("aws4_request"))
-
- signature2 := aws.GetHMAC(hash, []byte(stringToSign)) // Get Signature HMAC SHA256
- signature := hex.EncodeToString(signature2)
-
- // TASK 4: ADD SIGNING INFORMATION TO THE REQUEST.
- authorizationHeader := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
- algorithm, resIDMatch, credentialScope, signedHeaders, signature)
-
- req.Header.Add("Authorization", authorizationHeader)
- req.Header.Add("x-amz-date", amzDate)
- req.Header.Add("x-amz-security-token", resSessionMatch)
-
- req.URL.RawQuery = params.Encode()
-
- client := s.verificationClient
- if client == nil {
- client = defaultVerificationClient
- }
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- identityInfo := aws.IdentityResponse{}
- if err := json.NewDecoder(res.Body).Decode(&identityInfo); err != nil {
- return false, nil, err
- }
-
- extraData := map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/",
- "account": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Account,
- "user_id": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.UserID,
- "arn": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Arn,
- }
- return true, extraData, nil
- } else if res.StatusCode == 403 {
- // Experimentation has indicated that if you make two GetCallerIdentity requests within five seconds that
- // share a key ID but are signed with different secrets the second one will be rejected with a 403 that
- // carries a SignatureDoesNotMatch code in its body. This happens even if the second ID-secret pair is
- // valid. Since this is exactly our access pattern, we need to work around it.
- //
- // Fortunately, experimentation has also revealed a workaround: simply resubmit the second request. The
- // response to the resubmission will be as expected. But there's a caveat: You can't have closed the body of
- // the response to the original second request, or read to its end, or the resubmission will also yield a
- // SignatureDoesNotMatch. For this reason, we have to re-request all 403s. We can't re-request only
- // SignatureDoesNotMatch responses, because we can only tell whether a given 403 is a SignatureDoesNotMatch
- // after decoding its response body, which requires reading the entire response body, which disables the
- // workaround.
- //
- // We are clearly deep in the guts of AWS implementation details here, so this all might change with no
- // notice. If you're here because something in this detector broke, you have my condolences.
- if retryOn403 {
- return s.verifyMatch(ctx, resIDMatch, resSecretMatch, resSessionMatch, false)
- }
-
- var body aws.ErrorResponseBody
- if err = json.NewDecoder(res.Body).Decode(&body); err != nil {
- return false, nil, fmt.Errorf("couldn't parse the sts response body (%v)", err)
- }
- // All instances of the code I've seen in the wild are PascalCased but this check is
- // case-insensitive out of an abundance of caution
- if strings.EqualFold(body.Error.Code, "InvalidClientTokenId") {
- return false, nil, nil
- } else if strings.EqualFold(body.Error.Code, "ExpiredToken") {
- // ExpiredToken: The security token included in the request is expired
- return false, nil, nil
- }
- return false, nil, fmt.Errorf("request to %v returned status %d with an unexpected reason (%s: %s)", res.Request.URL, res.StatusCode, body.Error.Code, body.Error.Message)
- } else {
- return false, nil, fmt.Errorf("request to %v returned unexpected status %d", res.Request.URL, res.StatusCode)
- }
-}
-
-func (s scanner) CleanResults(results []detectors.Result) []detectors.Result {
- return aws.CleanResults(results)
-}
-
-// Reference: https://nitter.poast.org/TalBeerySec/status/1816449053841838223#m
-func checkSessionToken(sessionToken string, secret string) bool {
- if !(strings.Contains(sessionToken, "YXdz") || strings.Contains(sessionToken, "Jb3JpZ2luX2Vj")) ||
- strings.Contains(sessionToken, secret) {
- // Handle error if the sessionToken is not a valid base64 string
- return false
- }
- return true
-}
-
-func (s scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AWSSessionKey
-}
-
-func (s scanner) Description() string {
- return "AWS (Amazon Web Services) is a comprehensive cloud computing platform offering a wide range of on-demand services like computing power, storage, databases. API keys for AWS can have varying amount of access to these services depending on the IAM policy attached. AWS Session Tokens are short-lived keys."
-}
diff --git a/pkg/detectors/aws/session_keys/sessionkeys_test.go b/pkg/detectors/aws/session_keys/sessionkeys_test.go
deleted file mode 100644
index b884dcb3e8f2..000000000000
--- a/pkg/detectors/aws/session_keys/sessionkeys_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package session_keys
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAWSSessionKey_Pattern(t *testing.T) {
- d := New()
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- aws credentials{
- id: ASIABBKK02W42Q3IPSPG
- secret: fkhIiUwQY32Zu9e4a86g9r3WpTzfE1aXljVcgn8O
- session: aSqfp/GTZbJP+tXPNCZ9GoveoM0vgxtlYXdzPQ2uYNMPPgUkt0VT7SoTLasAo7iVqWWREOUC6DEenlcgDEKyzIEgQW5Ju/b9K/Z176uD2HJYCfq/lyowHtt5PvJi7LRuf/urSorGbTcqNUvPi42YP1Ps/4F6He9hQA1io3EAGBC3ICGHXWf2IlvFoTNUyPTqhjnPEKMWZ42jblqNAdD7hLpzNXmmGhdLCjy99XK8+gjHdZHkOeD/FIjRPRZ7Jl0tdwdqFEwzRVCzL2uelMVMd3UaZ+d4I4Kf+J464piO//jxx48Fs/mG3zr5ba9m2S+6gvUZJq4j+0uJ+jf6cG/x2G9XSybqYQRwvxfNquKB4TcKiGVH5+ZbJT4ASkARadwoSPMGfvMPje+X2zAziSzXfsxYfIQKf6iJ9p7VavlDGi+Acr4kwFXW5IfQs4uGk6AVQFsoZK3o1hhLOkuOwWQEWhDQGNLXwJbFqXfELOnUQvM0Z5NUm46bjAAi4g+X9gLPNR/KjzXuuTTaWYrQEjXLb7PxS0sIttAb1w+sTXXtc1kDIsABC6KcsyGlEwji5sLkbkUa=
- }
- `,
- want: []string{"ASIABBKK02W42Q3IPSPG:fkhIiUwQY32Zu9e4a86g9r3WpTzfE1aXljVcgn8O:aSqfp/GTZbJP+tXPNCZ9GoveoM0vgxtlYXdzPQ2uYNMPPgUkt0VT7SoTLasAo7iVqWWREOUC6DEenlcgDEKyzIEgQW5Ju/b9K/Z176uD2HJYCfq/lyowHtt5PvJi7LRuf/urSorGbTcqNUvPi42YP1Ps/4F6He9hQA1io3EAGBC3ICGHXWf2IlvFoTNUyPTqhjnPEKMWZ42jblqNAdD7hLpzNXmmGhdLCjy99XK8+gjHdZHkOeD/FIjRPRZ7Jl0tdwdqFEwzRVCzL2uelMVMd3UaZ+d4I4Kf+J464piO//jxx48Fs/mG3zr5ba9m2S+6gvUZJq4j+0uJ+jf6cG/x2G9XSybqYQRwvxfNquKB4TcKiGVH5+ZbJT4ASkARadwoSPMGfvMPje+X2zAziSzXfsxYfIQKf6iJ9p7VavlDGi+Acr4kwFXW5IfQs4uGk6AVQFsoZK3o1hhLOkuOwWQEWhDQGNLXwJbFqXfELOnUQvM0Z5NUm46bjAAi4g+X9gLPNR/KjzXuuTTaWYrQEjXLb7PxS0sIttAb1w+sTXXtc1kDIsABC6KcsyGlEwji5sLkbkUa="},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {ASIABBKK02W42Q3IPSPG}
- {AQAAABAAA fkhIiUwQY32Zu9e4a86g9r3WpTzfE1aXljVcgn8O}
- {AQAAABAAA aSqfp/GTZbJP+tXPNCZ9GoveoM0vgxtlYXdzPQ2uYNMPPgUkt0VT7SoTLasAo7iVqWWREOUC6DEenlcgDEKyzIEgQW5Ju/b9K/Z176uD2HJYCfq/lyowHtt5PvJi7LRuf/urSorGbTcqNUvPi42YP1Ps/4F6He9hQA1io3EAGBC3ICGHXWf2IlvFoTNUyPTqhjnPEKMWZ42jblqNAdD7hLpzNXmmGhdLCjy99XK8+gjHdZHkOeD/FIjRPRZ7Jl0tdwdqFEwzRVCzL2uelMVMd3UaZ+d4I4Kf+J464piO//jxx48Fs/mG3zr5ba9m2S+6gvUZJq4j+0uJ+jf6cG/x2G9XSybqYQRwvxfNquKB4TcKiGVH5+ZbJT4ASkARadwoSPMGfvMPje+X2zAziSzXfsxYfIQKf6iJ9p7VavlDGi+Acr4kwFXW5IfQs4uGk6AVQFsoZK3o1hhLOkuOwWQEWhDQGNLXwJbFqXfELOnUQvM0Z5NUm46bjAAi4g+X9gLPNR/KjzXuuTTaWYrQEjXLb7PxS0sIttAb1w+sTXXtc1kDIsABC6KcsyGlEwji5sLkbkUa=}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"ASIABBKK02W42Q3IPSPG:fkhIiUwQY32Zu9e4a86g9r3WpTzfE1aXljVcgn8O:aSqfp/GTZbJP+tXPNCZ9GoveoM0vgxtlYXdzPQ2uYNMPPgUkt0VT7SoTLasAo7iVqWWREOUC6DEenlcgDEKyzIEgQW5Ju/b9K/Z176uD2HJYCfq/lyowHtt5PvJi7LRuf/urSorGbTcqNUvPi42YP1Ps/4F6He9hQA1io3EAGBC3ICGHXWf2IlvFoTNUyPTqhjnPEKMWZ42jblqNAdD7hLpzNXmmGhdLCjy99XK8+gjHdZHkOeD/FIjRPRZ7Jl0tdwdqFEwzRVCzL2uelMVMd3UaZ+d4I4Kf+J464piO//jxx48Fs/mG3zr5ba9m2S+6gvUZJq4j+0uJ+jf6cG/x2G9XSybqYQRwvxfNquKB4TcKiGVH5+ZbJT4ASkARadwoSPMGfvMPje+X2zAziSzXfsxYfIQKf6iJ9p7VavlDGi+Acr4kwFXW5IfQs4uGk6AVQFsoZK3o1hhLOkuOwWQEWhDQGNLXwJbFqXfELOnUQvM0Z5NUm46bjAAi4g+X9gLPNR/KjzXuuTTaWYrQEjXLb7PxS0sIttAb1w+sTXXtc1kDIsABC6KcsyGlEwji5sLkbkUa="},
- },
- {
- name: "invalid pattern",
- input: `
- aws credentials{
- id: ASIABBKK02W42Q3IPSPG
- secret: $YenOG.PKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63;
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
-
-func TestAWSSessionKey_WithAllowedAccounts(t *testing.T) {
- accounts := []string{"123456789012", "999888777666"}
- s := New(WithAllowedAccounts(accounts))
-
- // Test that allowed accounts are properly configured
- shouldSkip := s.ShouldSkipAccount("123456789012")
- require.False(t, shouldSkip)
- require.True(t, s.IsInAllowList("123456789012"))
-
- // Test that non-allowed accounts are skipped
- shouldSkip = s.ShouldSkipAccount("111222333444")
- require.True(t, shouldSkip)
- require.False(t, s.IsInAllowList("111222333444"))
-}
-
-func TestAWSSessionKey_WithDeniedAccounts(t *testing.T) {
- accounts := []string{"123456789012", "999888777666"}
- s := New(WithDeniedAccounts(accounts))
-
- // Test that denied accounts are properly skipped
- shouldSkip := s.ShouldSkipAccount("123456789012")
- require.True(t, shouldSkip)
- require.True(t, s.IsInDenyList("123456789012"))
-
- // Test that non-denied accounts are not skipped
- shouldSkip = s.ShouldSkipAccount("111222333444")
- require.False(t, shouldSkip)
- require.False(t, s.IsInDenyList("111222333444"))
-}
diff --git a/pkg/detectors/aws/utils.go b/pkg/detectors/aws/utils.go
deleted file mode 100644
index 468562b557b2..000000000000
--- a/pkg/detectors/aws/utils.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package aws
-
-import (
- "crypto/hmac"
- "crypto/sha256"
- "encoding/base32"
- "encoding/binary"
- "encoding/hex"
- "fmt"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-)
-
-// ResourceTypes derived from: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids
-var ResourceTypes = map[string]string{
- "ABIA": "AWS STS service bearer token",
- "ACCA": "Context-specific credential",
- "AGPA": "User group",
- "AIDA": "IAM user",
- "AIPA": "Amazon EC2 instance profile",
- "AKIA": "Access key",
- "ANPA": "Managed policy",
- "ANVA": "Version in a managed policy",
- "APKA": "Public key",
- "AROA": "Role",
- "ASCA": "Certificate",
- "ASIA": "Temporary (AWS STS) access key IDs",
-}
-
-// UrlEncodedReplacer helps capture base64-encoded results that may be url-encoded.
-// TODO: Add this as a decoder, or make it a more generic.
-var UrlEncodedReplacer = strings.NewReplacer(
- "%2B", "+",
- "%2b", "+",
- "%2F", "/",
- "%2f", "/",
- "%3d", "=",
- "%3D", "=",
-)
-
-// Hashes, like those for git, do technically match the secret pattern.
-// But they are extremely unlikely to be generated as an actual AWS secret.
-// So when we find them, if they're not verified, we should ignore the result.
-var FalsePositiveSecretPat = regexp.MustCompile(`[a-f0-9]{40}`)
-
-func GetAccountNumFromID(id string) (string, error) {
- // Function to get the account number from an AWS ID (no verification required)
- // Source: https://medium.com/@TalBeerySec/a-short-note-on-aws-key-id-f88cc4317489
- if len(id) < 4 {
- return "", fmt.Errorf("AWSID is too short")
- }
- if id[4] == 'I' || id[4] == 'J' {
- return "", fmt.Errorf("can't get account number from AKIAJ/ASIAJ or AKIAI/ASIAI keys")
- }
- trimmedAWSID := id[4:]
- decodedBytes, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(trimmedAWSID))
- if err != nil {
- return "", err
- }
-
- if len(decodedBytes) < 6 {
- return "", fmt.Errorf("decoded AWSID is too short")
- }
-
- data := make([]byte, 8)
- copy(data[2:], decodedBytes[0:6])
- z := binary.BigEndian.Uint64(data)
- const mask uint64 = 0x7fffffffff80
- accountNum := (z & mask) >> 7
- return fmt.Sprintf("%012d", accountNum), nil
-}
-
-func GetHash(input string) string {
- data := []byte(input)
- hasher := sha256.New()
- hasher.Write(data)
- return hex.EncodeToString(hasher.Sum(nil))
-}
-
-func GetHMAC(key []byte, data []byte) []byte {
- hasher := hmac.New(sha256.New, key)
- hasher.Write(data)
- return hasher.Sum(nil)
-}
-
-func CleanResults(results []detectors.Result) []detectors.Result {
- if len(results) == 0 {
- return results
- }
-
- // For every ID, we want at most one result, preferably verified.
- idResults := map[string]detectors.Result{}
- for _, result := range results {
- // Always accept the verified result as the result for the given ID.
- if result.Verified {
- idResults[result.Redacted] = result
- continue
- }
-
- // Only include an unverified result if we don't already have a result for a given ID.
- if _, exist := idResults[result.Redacted]; !exist {
- idResults[result.Redacted] = result
- }
- }
-
- var out []detectors.Result
- for _, r := range idResults {
- out = append(out, r)
- }
- return out
-}
diff --git a/pkg/detectors/axonaut/axonaut.go b/pkg/detectors/axonaut/axonaut.go
deleted file mode 100644
index 04a4549f419e..000000000000
--- a/pkg/detectors/axonaut/axonaut.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package axonaut
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"axonaut"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"axonaut"}
-}
-
-// FromData will find and optionally verify Axonaut secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Axonaut,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://axonaut.com/api/v2/companies?type=all&sort=id", http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("userApiKey", key)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Axonaut
-}
-
-func (s Scanner) Description() string {
- return "Axonaut is a service that provides business management solutions including CRM, invoicing, and accounting. Axonaut API keys can be used to access and manage business data through their API."
-}
diff --git a/pkg/detectors/axonaut/axonaut_integration_test.go b/pkg/detectors/axonaut/axonaut_integration_test.go
deleted file mode 100644
index 0d64cbc64e1e..000000000000
--- a/pkg/detectors/axonaut/axonaut_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package axonaut
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAxonaut_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AXONAUT")
- inactiveSecret := testSecrets.MustGetField("AXONAUT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a axonaut secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Axonaut,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a axonaut secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Axonaut,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Axonaut.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Axonaut.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/axonaut/axonaut_test.go b/pkg/detectors/axonaut/axonaut_test.go
deleted file mode 100644
index 522d5ae06512..000000000000
--- a/pkg/detectors/axonaut/axonaut_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package axonaut
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAxonaut_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the axonaut API
- [DEBUG] Using Key=4ve4aj6v38uiadaq9hcgpupp2b3lh2k8
- [INFO] Response received: 200 OK
- `,
- want: []string{"4ve4aj6v38uiadaq9hcgpupp2b3lh2k8"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {axonaut}
- {AQAAABAAA m7mnuk7p3buc87b2ok29e7ykp2xqkkx0}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"m7mnuk7p3buc87b2ok29e7ykp2xqkkx0"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the axonaut API
- [DEBUG] Using Key=ASIABBKK02W42Q3IPSPG
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/aylien/aylien.go b/pkg/detectors/aylien/aylien.go
deleted file mode 100644
index 1006b7f597f6..000000000000
--- a/pkg/detectors/aylien/aylien.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package aylien
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"aylien"}) + `\b([a-z0-9]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"aylien"}) + `\b([a-z0-9]{8})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"aylien"}
-}
-
-// FromData will find and optionally verify Aylien secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- resIdMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Aylien,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
- if verify {
- isVerified, err := verifyMatch(ctx, client, resIdMatch, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, id, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.aylien.com/news/stories", http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("X-AYLIEN-NewsAPI-Application-ID", id)
- req.Header.Add("X-AYLIEN-NewsAPI-Application-Key", key)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Aylien
-}
-
-func (s Scanner) Description() string {
- return "Aylien is a text analysis platform that provides natural language processing and machine learning APIs. Aylien API keys can be used to access and analyze text data."
-}
diff --git a/pkg/detectors/aylien/aylien_integration_test.go b/pkg/detectors/aylien/aylien_integration_test.go
deleted file mode 100644
index e1315f5f9b66..000000000000
--- a/pkg/detectors/aylien/aylien_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package aylien
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAylien_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AYLIEN")
- id := testSecrets.MustGetField("AYLIEN_ID")
- inactiveSecret := testSecrets.MustGetField("AYLIEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aylien secret %s within aylien %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Aylien,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a aylien secret %s within aylien %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Aylien,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Aylien.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Aylien.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/aylien/aylien_test.go b/pkg/detectors/aylien/aylien_test.go
deleted file mode 100644
index 14cc14baee22..000000000000
--- a/pkg/detectors/aylien/aylien_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package aylien
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAylien_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- # do not share these credentials
- aylien credentials:
- aylien key: cr479du2l9pkmhar8gw5hufofvwp86q9
- aylien id: y3ejw028
- # valid till Dec 2025
- `,
- want: []string{"cr479du2l9pkmhar8gw5hufofvwp86q9y3ejw028"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {aylien wmxv7ckn}
- {aylien AQAAABAAA i09t8rb5r7otvq8sdrfjunakcso157mh}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"i09t8rb5r7otvq8sdrfjunakcso157mhwmxv7ckn"},
- },
- {
- name: "invalid pattern",
- input: `
- # do not share these credentials
- aylien credentials:
- aylien key: cr4U9du2l9pkmhar8gw5hufofvWp86q9
- aylien id: y3ejwA8
- # valid till Dec 2025
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/ayrshare/ayrshare.go b/pkg/detectors/ayrshare/ayrshare.go
deleted file mode 100644
index 6ac4e3ae0d03..000000000000
--- a/pkg/detectors/ayrshare/ayrshare.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package ayrshare
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ayrshare"}) + `\b([A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"ayrshare"}
-}
-
-// FromData will find and optionally verify Ayrshare secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Ayrshare,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, extraData, err := verifyMatch(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, key string) (bool, map[string]string, error) {
- // Reference: https://www.ayrshare.com/docs/apis/user/profile-details
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.ayrshare.com/api/user", http.NoBody)
- if err != nil {
- return false, nil, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, nil, err
- }
- var responseBody map[string]any
- if err := json.Unmarshal(bodyBytes, &responseBody); err == nil {
- if email, ok := responseBody["email"].(string); ok {
- return true, map[string]string{"email": email}, nil
- }
- }
- return true, nil, nil
- case http.StatusUnauthorized:
- return false, nil, nil
- case http.StatusForbidden:
- // Invalid Bearer tokens get a 403 Forbidden response despite what is stated in the docs.
- // Documentation: https://www.ayrshare.com/docs/errors/errors-http
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, nil, err
- }
- if strings.Contains(string(bodyBytes), "API Key not valid") {
- return false, nil, nil
- }
- }
- return false, nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Ayrshare
-}
-
-func (s Scanner) Description() string {
- return "Ayrshare provides social media management services. Ayrshare API keys can be used to manage social media accounts and posts."
-}
diff --git a/pkg/detectors/ayrshare/ayrshare_integration_test.go b/pkg/detectors/ayrshare/ayrshare_integration_test.go
deleted file mode 100644
index da11fac95ea7..000000000000
--- a/pkg/detectors/ayrshare/ayrshare_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package ayrshare
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAyrshare_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AYRSHARE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("AYRSHARE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ayrshare secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Ayrshare,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ayrshare secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Ayrshare,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Ayrshare.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Ayrshare.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/ayrshare/ayrshare_test.go b/pkg/detectors/ayrshare/ayrshare_test.go
deleted file mode 100644
index e26c712629a4..000000000000
--- a/pkg/detectors/ayrshare/ayrshare_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package ayrshare
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAyrShare_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the ayrshare API
- [DEBUG] Using Key=2FTJTA1C-BXO0DV4J-HGTP9E62-QHQSILY1
- [INFO] Response received: 200 OK
- `,
- want: []string{"2FTJTA1C-BXO0DV4J-HGTP9E62-QHQSILY1"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {ayrshare}
- {AQAAABAAA I1WPQLUQ-NCNHEI13-1MF4HJZQ-EEDDVZYO}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"I1WPQLUQ-NCNHEI13-1MF4HJZQ-EEDDVZYO"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the ayrshare API
- [DEBUG] Using Key=KRXaU9GK3f[yHG1FS$]bwhsIXdW22epH
- [ERROR] Response received: 401 UnAuthorized
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_batch/azurebatch.go b/pkg/detectors/azure_batch/azurebatch.go
deleted file mode 100644
index ad2110da281e..000000000000
--- a/pkg/detectors/azure_batch/azurebatch.go
+++ /dev/null
@@ -1,138 +0,0 @@
-package azure_batch
-
-import (
- "context"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/base64"
- "fmt"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)
-
-var (
- defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- urlPat = regexp.MustCompile(`https://(.{1,50})\.(.{1,50})\.batch\.azure\.com`)
- secretPat = regexp.MustCompile(`[A-Za-z0-9+/=]{88}`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{".batch.azure.com"}
-}
-
-// FromData will find and optionally verify Azurebatch secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- urlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, urlMatch := range urlMatches {
-
- for _, secretMatch := range secretMatches {
-
- endpoint := urlMatch[0]
- accountName := urlMatch[1]
- accountKey := secretMatch[0]
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureBatch,
- Raw: []byte(endpoint),
- RawV2: []byte(endpoint + accountKey),
- Redacted: endpoint,
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- isVerified, err := verifyMatch(ctx, client, endpoint, accountName, accountKey)
- s1.Verified = isVerified
- s1.SetVerificationError(err)
- }
-
- results = append(results, s1)
- if s1.Verified {
- break
- }
- }
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, endpoint, accountName, accountKey string) (bool, error) {
- // Reference: https://learn.microsoft.com/en-us/rest/api/batchservice/application/list
- url := fmt.Sprintf("%s/applications?api-version=2020-09-01.12.0", endpoint)
-
- date := time.Now().UTC().Format(http.TimeFormat)
- stringToSign := fmt.Sprintf(
- "GET\n\n\n\n\napplication/json\n%s\n\n\n\n\n\n%s\napi-version:%s",
- date,
- strings.ToLower(fmt.Sprintf("/%s/applications", accountName)),
- "2020-09-01.12.0",
- )
- key, _ := base64.StdEncoding.DecodeString(accountKey)
- h := hmac.New(sha256.New, key)
- h.Write([]byte(stringToSign))
- signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Authorization", fmt.Sprintf("SharedKey %s:%s", accountName, signature))
- req.Header.Set("Date", date)
- resp, err := client.Do(req)
- if err != nil {
- // If the host is not found, we can assume that the endpoint is invalid
- if strings.Contains(err.Error(), "no such host") {
- return false, nil
- }
- return false, err
- }
- defer resp.Body.Close()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusForbidden:
- // Key is either invalid or the account is disabled.
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d for %s", resp.StatusCode, url)
- }
-}
-
-func (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {
- return false, ""
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureBatch
-}
-
-func (s Scanner) Description() string {
- return "Azure Batch is a cloud service that provides large-scale parallel and high-performance computing (HPC) applications efficiently in the cloud. Azure Batch account keys can be used to manage and control access to these resources."
-}
diff --git a/pkg/detectors/azure_batch/azurebatch_integration_test.go b/pkg/detectors/azure_batch/azurebatch_integration_test.go
deleted file mode 100644
index 6af005311d70..000000000000
--- a/pkg/detectors/azure_batch/azurebatch_integration_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azure_batch
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzurebatch_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- url := testSecrets.MustGetField("AZUREBATCH_URL")
- secret := testSecrets.MustGetField("AZUREBATCH_KEY")
- inactiveSecret := testSecrets.MustGetField("AZUREBATCH_KEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurebatch secret %s and %s within", url, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureBatch,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurebatch secret %s and %s within but not valid", url, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureBatch,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AzureBatch.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "RawV2", "Raw", "Redacted", "verificationError")
-
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AzureBatch.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_batch/azurebatch_test.go b/pkg/detectors/azure_batch/azurebatch_test.go
deleted file mode 100644
index 058473f2fdef..000000000000
--- a/pkg/detectors/azure_batch/azurebatch_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package azure_batch
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureBatch_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] Sending request to the ayrshare API
- [DEBUG] Using Secret = BXIMbhBlC3=5hIbqCEKvq7opaV2ZfO0XWbcnasZmPm/AJfQqdcnt/AVmKkJ8Qw80Zc1rQDaw+2Ytxc1hDq1m/LB0
- [INFO] https://JrxlYxT+0hW.YSA.batch.azure.com
- [INFO] Response received: 200 OK
- `,
- want: []string{"https://JrxlYxT+0hW.YSA.batch.azure.comBXIMbhBlC3=5hIbqCEKvq7opaV2ZfO0XWbcnasZmPm/AJfQqdcnt/AVmKkJ8Qw80Zc1rQDaw+2Ytxc1hDq1m/LB0"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {https://pb0bik2a59qznkh87pdd6twjlgzpmxz.pfv9bpr2hujs.batch.azure.com}
- {AQAAABAAA XJc2nGZvqPAXYfHxsiwUDBA4ynHzGc9nQl1Ih16lk19=2+qqeJUDp5eBxWVrE0LQYlnbeu/orbEtblFL218S4Wko}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"https://pb0bik2a59qznkh87pdd6twjlgzpmxz.pfv9bpr2hujs.batch.azure.comXJc2nGZvqPAXYfHxsiwUDBA4ynHzGc9nQl1Ih16lk19=2+qqeJUDp5eBxWVrE0LQYlnbeu/orbEtblFL218S4Wko"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] Sending request to the ayrshare API
- [DEBUG] Using Secret=BXIMbhBlC3=5hIbqCEKvq7op!V2ZfO0XWbcnasZmPm/AJfQqdcnt/AVmKkJ8Qw80Zc1rQDaw+2Ytxc1hDq1m/
- [INFO] http://invalid.this.batch.azure.com
- [INFO] Response received: 200 OK
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_cosmosdb/azure_cosmosdb.go b/pkg/detectors/azure_cosmosdb/azure_cosmosdb.go
deleted file mode 100644
index 906a86119e96..000000000000
--- a/pkg/detectors/azure_cosmosdb/azure_cosmosdb.go
+++ /dev/null
@@ -1,184 +0,0 @@
-package azure_cosmosdb
-
-import (
- "context"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/base64"
- "errors"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "regexp"
- "strings"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-var (
- defaultClient = common.SaneHttpClient()
-
- dbKeyPattern = regexp.MustCompile(`([A-Za-z0-9]{86}==)`)
- // account name can contain only lowercase letters, numbers and the `-` character, must be between 3 and 44 characters long.
- accountUrlPattern = regexp.MustCompile(`([a-z0-9-]{3,44}\.(?:documents|table\.cosmos)\.azure\.com)`)
-
- invalidHosts = simple.NewCache[struct{}]()
-
- errNoHost = errors.New("no such host")
-)
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
-
- return defaultClient
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureCosmosDBKeyIdentifiable
-}
-
-func (s Scanner) Description() string {
- return "Azure Cosmos DB is a globally distributed, multi-model database service offered by Microsoft. CosmosDB keys and connection string are used to connect with Cosmos DB."
-}
-
-func (s Scanner) Keywords() []string {
- return []string{".documents.azure.com", ".table.cosmos.azure.com"}
-}
-
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeyMatches, uniqueAccountMatches = make(map[string]struct{}), make(map[string]struct{})
-
- for _, match := range dbKeyPattern.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeyMatches[match[1]] = struct{}{}
- }
-
- for _, match := range accountUrlPattern.FindAllStringSubmatch(dataStr, -1) {
- uniqueAccountMatches[match[1]] = struct{}{}
- }
-
- for key := range uniqueKeyMatches {
- for accountUrl := range uniqueAccountMatches {
- if invalidHosts.Exists(accountUrl) {
- delete(uniqueAccountMatches, accountUrl)
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureCosmosDBKeyIdentifiable,
- Raw: []byte(key),
- RawV2: []byte("key: " + key + " account_url: " + accountUrl), // key: account_url:
- ExtraData: map[string]string{},
- }
-
- if verify {
- var verified bool
- var verificationErr error
-
- client := s.getClient()
-
- // perform verification based on db type
- if strings.Contains(accountUrl, ".documents.azure.com") {
- verified, verificationErr = verifyCosmosDocumentDB(client, accountUrl, key)
- s1.ExtraData["DB Type"] = "Document"
-
- } else if strings.Contains(accountUrl, ".table.cosmos.azure.com") {
- verified, verificationErr = verifyCosmosTableDB(client, accountUrl, key)
- s1.ExtraData["DB Type"] = "Table"
- }
-
- s1.Verified = verified
- if verificationErr != nil {
- if errors.Is(verificationErr, errNoHost) {
- invalidHosts.Set(accountUrl, struct{}{})
- continue
- }
-
- s1.SetVerificationError(verificationErr)
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-// documentation: https://learn.microsoft.com/en-us/rest/api/cosmos-db/list-databases
-func verifyCosmosDocumentDB(client *http.Client, accountUrl, key string) (bool, error) {
- // decode the base64 encoded key
- decodedKey, err := base64.StdEncoding.DecodeString(key)
- if err != nil {
- return false, fmt.Errorf("failed to decode key: %v", err)
- }
-
- req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://%s:443/dbs", accountUrl), nil)
- if err != nil {
- return false, fmt.Errorf("failed to create request: %v", err)
- }
-
- dateRFC1123 := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")
- authHeader := fmt.Sprintf("type=master&ver=1.0&sig=%s", url.QueryEscape(createDocumentsSignature(decodedKey, dateRFC1123)))
-
- // required headers
- // docs: https://learn.microsoft.com/en-us/rest/api/cosmos-db/common-cosmosdb-rest-request-headers
- req.Header.Set("Authorization", authHeader)
- req.Header.Set("x-ms-date", dateRFC1123)
- req.Header.Set("x-ms-version", "2018-12-31")
-
- resp, err := client.Do(req)
- if err != nil {
- // lookup foo.documents.azure.com: no such host
- if strings.Contains(err.Error(), "no such host") {
- return false, errNoHost
- }
-
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- // Check response status code
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-func createDocumentsSignature(decodedKey []byte, dateRFC1123 string) string {
- stringToSign := fmt.Sprintf(
- "%s\n%s\n%s\n%s\n\n",
- strings.ToLower(http.MethodGet),
- strings.ToLower("dbs"),
- "",
- strings.ToLower(dateRFC1123),
- )
-
- // compute HMAC-SHA256 signature
- mac := hmac.New(sha256.New, decodedKey)
- mac.Write([]byte(stringToSign))
-
- return base64.StdEncoding.EncodeToString(mac.Sum(nil))
-}
diff --git a/pkg/detectors/azure_cosmosdb/azure_cosmosdb_integration_test.go b/pkg/detectors/azure_cosmosdb/azure_cosmosdb_integration_test.go
deleted file mode 100644
index 0929cc40b1c5..000000000000
--- a/pkg/detectors/azure_cosmosdb/azure_cosmosdb_integration_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azure_cosmosdb
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCosmosDB_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- key := testSecrets.MustGetField("COSMOSDB_KEY")
- accountUrl := testSecrets.MustGetField("COSMOSDB_ACCOUNT")
- inactiveKey := testSecrets.MustGetField("COSMOSDB_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte(fmt.Sprintf("You can find a cosmosdb key: %s and account url: %s within", key, accountUrl)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureCosmosDBKeyIdentifiable,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte(fmt.Sprintf("You can find a cosmosdb key: %s and accounturl: %s within but not valid", inactiveKey, accountUrl)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureCosmosDBKeyIdentifiable,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CosmosDB.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("CosmosDB.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_cosmosdb/azure_cosmosdb_test.go b/pkg/detectors/azure_cosmosdb/azure_cosmosdb_test.go
deleted file mode 100644
index c4c1740a5590..000000000000
--- a/pkg/detectors/azure_cosmosdb/azure_cosmosdb_test.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package azure_cosmosdb
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestCosmosDB_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid document db pattern",
- input: `
- Cluster name: Cluster name must be at least 3 characters and at most 40 characters.
- Cluster name must only contain lowercase letters, numbers, and hyphens.
- The cluster name must not start or end in a hyphen.
- // config
- cosmosKey: FakeeP35zYGPXaEUfakeU7S8kcOY7NI7id8ddbHfakeAifake8Bbql1mXhMF2t0wQ0FAKEPQrwZZACDb3msoAg==
- https://trufflesecurity-fake.documents.azure.com:443`,
- want: []string{"key: FakeeP35zYGPXaEUfakeU7S8kcOY7NI7id8ddbHfakeAifake8Bbql1mXhMF2t0wQ0FAKEPQrwZZACDb3msoAg== account_url: trufflesecurity-fake.documents.azure.com"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {jc0338vpo7bd3rn99vu2trdbo.table.cosmos.azure.com}
- {AQAAABAAA tiHd2l1I3MptBj4s1zomhyIAuCJmR1bzxvGluBVW2k0JJ7Z6vmybKYiM7OY5HtDkvLVxyDD2ACW0GW2fug0cET==}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"key: tiHd2l1I3MptBj4s1zomhyIAuCJmR1bzxvGluBVW2k0JJ7Z6vmybKYiM7OY5HtDkvLVxyDD2ACW0GW2fug0cET== account_url: jc0338vpo7bd3rn99vu2trdbo.table.cosmos.azure.com"},
- },
- {
- name: "valid table db pattern",
- input: `
- Cluster name: Cluster name must be at least 3 characters and at most 40 characters.
- Cluster name must only contain lowercase letters, numbers, and hyphens.
- The cluster name must not start or end in a hyphen.
- // config
- cosmosKey: FakeeP35zYGPXaEUfakeU7S8kcOY7NI7id8ddbHfakeAifake8Bbql1mXhMF2t0wQ0FAKEPQrwZZACDb3msoAg==
- https://trufflesecurity-fake.table.cosmos.azure.com:443`,
- want: []string{"key: FakeeP35zYGPXaEUfakeU7S8kcOY7NI7id8ddbHfakeAifake8Bbql1mXhMF2t0wQ0FAKEPQrwZZACDb3msoAg== account_url: trufflesecurity-fake.table.cosmos.azure.com"},
- },
- {
- name: "invalid pattern",
- input: `
- FakeeP35zYGPXaEUfakeU7S8kcOY7I7id8ddbHfakeAifake8Bbql1mXhMF2t0wQ0FAKEPQrwZZACDb3msoAg==
- https://not-a-host.documents.azure.com:443`,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_cosmosdb/table.go b/pkg/detectors/azure_cosmosdb/table.go
deleted file mode 100644
index 157b205c4dda..000000000000
--- a/pkg/detectors/azure_cosmosdb/table.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package azure_cosmosdb
-
-import (
- "crypto/hmac"
- "crypto/sha256"
- "encoding/base64"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
-)
-
-func verifyCosmosTableDB(client *http.Client, accountUrl, key string) (bool, error) {
- // decode the base64 encoded key
- decodedKey, err := base64.StdEncoding.DecodeString(key)
- if err != nil {
- return false, fmt.Errorf("failed to decode key: %v", err)
- }
-
- req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://%s:443/Tables", accountUrl), nil)
- if err != nil {
- return false, fmt.Errorf("failed to create request: %v", err)
- }
-
- // extract abc123 from abc123.table.cosmos.azure.com
- accountName := strings.TrimPrefix(accountUrl, ".table.cosmos.azure.com")
-
- dateRFC1123 := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")
- authHeader := fmt.Sprintf("SharedKeyLite %s:%s", accountName, createTablesSignature(decodedKey, accountName, dateRFC1123))
-
- // required headers
- // docs: https://learn.microsoft.com/en-us/rest/api/cosmos-db/common-cosmosdb-rest-request-headers
- req.Header.Set("Authorization", authHeader)
- req.Header.Set("x-ms-date", dateRFC1123)
- req.Header.Set("x-ms-version", "2019-02-02")
- req.Header.Set("Accept", "application/json")
-
- resp, err := client.Do(req)
- if err != nil {
- // lookup foo.table.cosmos.azure.com: no such host
- if strings.Contains(err.Error(), "no such host") {
- return false, errNoHost
- }
-
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- // Check response status code
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-func createTablesSignature(decodedKey []byte, accountName, dateRFC1123 string) string {
- // create string to sign (method + date)
- stringToSign := fmt.Sprintf("%s\n%s", dateRFC1123, fmt.Sprintf("/%s/Tables", accountName))
-
- // Compute HMAC-SHA256 signature
- h := hmac.New(sha256.New, decodedKey)
- h.Write([]byte(stringToSign))
- signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
-
- return signature
-}
diff --git a/pkg/detectors/azure_entra/common.go b/pkg/detectors/azure_entra/common.go
deleted file mode 100644
index 3ce5ceb12c95..000000000000
--- a/pkg/detectors/azure_entra/common.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package azure_entra
-
-import (
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
- "golang.org/x/sync/singleflight"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-)
-
-const uuidStr = `[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`
-
-var (
- // Tenants can be identified with a UUID or an `*.onmicrosoft.com` domain.
- //
- // See:
- // https://learn.microsoft.com/en-us/partner-center/account-settings/find-ids-and-domain-names#find-the-microsoft-azure-ad-tenant-id-and-primary-domain-name
- // https://learn.microsoft.com/en-us/microsoft-365/admin/setup/domains-faq?view=o365-worldwide#why-do-i-have-an--onmicrosoft-com--domain
- tenantIdPat = regexp.MustCompile(fmt.Sprintf(
- //language=regexp
- `(?i)(?:(?:login\.microsoftonline\.com/|(?:login|sts)\.windows\.net/|(?:t[ae]n[ae]nt(?:[ ._-]?id)?|\btid)(?:.|\s){0,60}?)(%s)|https?://(%s)|X-AnchorMailbox(?:.|\s){0,60}?@(%s)|/(%s)/(?:oauth2/v2\.0|B2C_1\w+|common|discovery|federationmetadata|kerberos|login|openid/|reprocess|resume|saml2|token|uxlogout|v2\.0|wsfed))`,
- uuidStr,
- uuidStr,
- uuidStr,
- uuidStr,
- ))
- tenantOnMicrosoftPat = regexp.MustCompile(`([\w-]+\.onmicrosoft\.com)`)
-
- clientIdPat = regexp.MustCompile(fmt.Sprintf(
- `(?i)(?:(?:app(?:lication)?|client)(?:[ ._-]?id)?|username| -u)(?:.|\s){0,45}?(%s)`, uuidStr))
-)
-
-// FindTenantIdMatches returns a list of potential tenant IDs in the provided |data|.
-func FindTenantIdMatches(data string) map[string]struct{} {
- uniqueMatches := make(map[string]struct{})
-
- for _, match := range tenantIdPat.FindAllStringSubmatch(data, -1) {
- var m string
- if match[1] != "" {
- m = strings.ToLower(match[1])
- } else if match[2] != "" {
- m = strings.ToLower(match[2])
- } else if match[3] != "" {
- m = strings.ToLower(match[3])
- } else if match[4] != "" {
- m = strings.ToLower(match[4])
- }
- if _, ok := detectors.UuidFalsePositives[detectors.FalsePositive(m)]; ok {
- continue
- } else if detectors.StringShannonEntropy(m) < 3 {
- continue
- }
- uniqueMatches[m] = struct{}{}
- }
- for _, match := range tenantOnMicrosoftPat.FindAllStringSubmatch(data, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
- return uniqueMatches
-}
-
-// FindClientIdMatches returns a list of potential client UUIDs in the provided |data|.
-func FindClientIdMatches(data string) map[string]struct{} {
- uniqueMatches := make(map[string]struct{})
- for _, match := range clientIdPat.FindAllStringSubmatch(data, -1) {
- m := strings.ToLower(match[1])
- if _, ok := detectors.UuidFalsePositives[detectors.FalsePositive(m)]; ok {
- continue
- } else if detectors.StringShannonEntropy(m) < 3 {
- continue
- }
- uniqueMatches[m] = struct{}{}
- }
- return uniqueMatches
-}
-
-var (
- tenantCache = simple.NewCache[bool]()
- tenantGroup singleflight.Group
-)
-
-// TenantExists returns whether the tenant exists according to Microsoft's well-known OpenID endpoint.
-func TenantExists(ctx context.Context, client *http.Client, tenant string) bool {
- // Use cached value where possible.
- if tenantExists, isCached := tenantCache.Get(tenant); isCached {
- return tenantExists
- }
-
- // https://www.codingexplorations.com/blog/understanding-singleflight-in-golang-a-solution-for-eliminating-redundant-work
- tenantExists, _, _ := tenantGroup.Do(tenant, func() (interface{}, error) {
- result := queryTenant(ctx, client, tenant)
- tenantCache.Set(tenant, result)
- return result, nil
- })
-
- return tenantExists.(bool)
-}
-
-func queryTenant(ctx context.Context, client *http.Client, tenant string) bool {
- logger := ctx.Logger().WithName("azure").WithValues("tenant", tenant)
-
- tenantUrl := fmt.Sprintf("https://login.microsoftonline.com/%s/.well-known/openid-configuration", tenant)
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, tenantUrl, nil)
- if err != nil {
- return false
- }
-
- res, err := client.Do(req)
- if err != nil {
- logger.Error(err, "Failed to check if tenant exists")
- return false
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true
- case http.StatusBadRequest:
- logger.V(4).Info("Tenant does not exist.")
- return false
- default:
- bodyBytes, _ := io.ReadAll(res.Body)
- logger.Error(nil, "WARNING: Unexpected response when checking if tenant exists", "status_code", res.StatusCode, "body", string(bodyBytes))
- return false
- }
-}
diff --git a/pkg/detectors/azure_entra/common_test.go b/pkg/detectors/azure_entra/common_test.go
deleted file mode 100644
index 9d3f1e5a0917..000000000000
--- a/pkg/detectors/azure_entra/common_test.go
+++ /dev/null
@@ -1,362 +0,0 @@
-package azure_entra
-
-import (
- "testing"
-
- "github.com/google/go-cmp/cmp"
-)
-
-type testCase struct {
- Input string
- Expected map[string]struct{}
-}
-
-func runPatTest(t *testing.T, tests map[string]testCase, matchFunc func(data string) map[string]struct{}) {
- t.Helper()
- for name, test := range tests {
- t.Run(name, func(t *testing.T) {
- matches := matchFunc(test.Input)
- if len(matches) == 0 {
- if len(test.Expected) != 0 {
- t.Fatalf("no matches found, expected: %v", test.Expected)
- return
- } else {
- return
- }
- }
-
- if diff := cmp.Diff(test.Expected, matches); diff != "" {
- t.Errorf("expected: %s, actual: %s", test.Expected, matches)
- return
- }
- })
- }
-}
-
-func Test_FindTenantIdMatches(t *testing.T) {
- cases := map[string]testCase{
- // Tenant ID
- "audience": {
- Input: `az offazure hyperv site create --location "eastus" --service-principal-identity-details \
- application-id="cbcfc473-97da-45dd-8a00-3612d1ddf35a" \
- audience="https://bced5192-08c4-4470-9a94-666fea59efb07/aadapp" `,
- Expected: map[string]struct{}{
- "bced5192-08c4-4470-9a94-666fea59efb0": {},
- },
- },
- "tenant": {
- Input: ` "cas.authn.azure-active-directory.login-url=https://login.microsoftonline.com/common/",
- "cas.authn.azure-active-directory.tenant=8e439f30-da7a-482c-bd23-e45d0a732000"`,
- Expected: map[string]struct{}{
- "8e439f30-da7a-482c-bd23-e45d0a732000": {},
- },
- },
- "tanentId": {
- Input: `azure.grantType=client_credentials
-azure.tanentId=029e3b51-60dd-47aa-81ad-3c15b389db86`,
- Expected: map[string]struct{}{
- "029e3b51-60dd-47aa-81ad-3c15b389db86": {},
- },
- },
- "tenantid": {
- Input: ` file:
- folder-location: test
- tenantid: ${vcap.services.user-authentication-service.credentials.tenantid:317fb200-a693-4062-a4fb-9d131fcd2d3c}`,
- Expected: map[string]struct{}{
- "317fb200-a693-4062-a4fb-9d131fcd2d3c": {},
- },
- },
- "tenant id": {
- Input: `1. Enter the tenant id "2ce99e96-b41b-47a0-b37c-16a22bceb8c0"`,
- Expected: map[string]struct{}{
- "2ce99e96-b41b-47a0-b37c-16a22bceb8c0": {},
- },
- },
- "tenant_id": {
- Input: `location = "eastus"
-subscription_id = "47ab1364-000d-4a53-838d-1537b1e3b49f"
-tenant_id = "57aabdfc-6ce0-4828-94a2-9abe277892ec"`,
- Expected: map[string]struct{}{
- "57aabdfc-6ce0-4828-94a2-9abe277892ec": {},
- },
- },
- "tenant-id": {
- Input: ` active-directory:
- enabled: true
- profile:
- tenant-id: c32654ed-6931-4bae-bb23-a8b9e420e0f4
- credential:`,
- Expected: map[string]struct{}{
- "c32654ed-6931-4bae-bb23-a8b9e420e0f4": {},
- },
- },
- "tid": {
- Input: ` "sub": "jIzit1WEdXqAH9KZXz-e-UcqsVa1pyPoh-2hw3xjEO4",
- "tenant_region_scope": "AS",
- "tid": "974fde14-c3a4-481b-9b03-cfce18213a07",
- "uti": "2Y26RWHsWEiqhD2vi_PFAg",`,
- Expected: map[string]struct{}{
- "974fde14-c3a4-481b-9b03-cfce18213a07": {},
- },
- },
- "login.microsoftonline.com": {
- Input: ` auth: {
- authority: 'https://login.microsoftonline.com/7bb339cb-e94c-4a85-884c-48ebd9bb28c3',
- redirectUri: 'http://localhost:8080/landing'
-`,
- Expected: map[string]struct{}{
- "7bb339cb-e94c-4a85-884c-48ebd9bb28c3": {},
- },
- },
- "login.windows.net": {
- Input: `az offazure hyperv site create --location "eastus" --service-principal-identity-details aad-authority="https://login.windows.net/7bb339cb-e94c-4a85-884c-48ebd9bb28c3" application-id="e9f013df-2a2a-4871-b766-e79867f30348" \'`,
- Expected: map[string]struct{}{
- "7bb339cb-e94c-4a85-884c-48ebd9bb28c3": {},
- },
- },
- "sts.windows.net": {
- Input: `{
- "aud": "00000003-0000-0000-c000-000000000000",
- "iss": "https://sts.windows.net/974fde14-c3a4-481b-9b03-cfce182c3a07/",
- "iat": 1641799220,`,
- Expected: map[string]struct{}{
- "974fde14-c3a4-481b-9b03-cfce182c3a07": {},
- },
- },
- "oauth paths": {
- Input: ` "authPath": "/9b4bfaea-dd1c-4add-b1de-e10f51c65fd3/oauth2/v2.0/authorize",
- /32896ed7-d559-401b-85cf-167143d61be0/B2C_1A_Tapio_Signin/v2.0
- /461858f4-9c0d-46e0-a9e6-aefc4889aad6/B2C_1_sign_up_or_sign_in/SelfAsserted?tx=S
- -ArgumentList "/3f548be2-31e9-4681-839e-bc80d461f367/common/oauth2/authorize"
- "jwks_uri": "/6babcaad-604b-40ac-a9d7-9fd97c0b779f/discovery/keys",
- MetadataLocation = "/b55f0c51-61a7-45c3-84df-33569b247796/federationmetadata/2007-06/federationmetadata.xml?appid=3245199b-1a5d-42df-93ce-e64ac7f5b938
- "kerberos_endpoint": "/a4067d12-2fc0-4367-a213-9e4031cbc173/kerberos",
- /b2326b8a-059d-48ca-96ac-8d8d5d841860/login
- "userinfo_endpoint": "/6ba4caad-604b-40ac-a9d7-9fd97c0b779f/openid/userinfo"
- …en-US","urlLogin":"/9673e9a8-aa57-4461-9336-5fd3f0034e18/reprocess?ctx=rQIIAZ2QvWvbQA…
- /6c912b97-d9f0-4472-a96a-d82de2f1d438/resume?ctx=rQIIAZVTP
- // /aa8306d8-5417-43cc-b8e8-7e77b918682c/v2.0/.well-known/openid-configuration
- // /051aeb51-408b-403b-b95c-4ff3b303a08a/token
- "/4a5378f9-29f4-4d3e-be89-669d03ada9d8/uxlogout"
- /dc38a67a-f981-4e24-ba16-4443ada44484/wsfed
-`,
- Expected: map[string]struct{}{
- "051aeb51-408b-403b-b95c-4ff3b303a08a": {},
- "32896ed7-d559-401b-85cf-167143d61be0": {},
- "3f548be2-31e9-4681-839e-bc80d461f367": {},
- "461858f4-9c0d-46e0-a9e6-aefc4889aad6": {},
- "4a5378f9-29f4-4d3e-be89-669d03ada9d8": {},
- "6ba4caad-604b-40ac-a9d7-9fd97c0b779f": {},
- "6babcaad-604b-40ac-a9d7-9fd97c0b779f": {},
- "6c912b97-d9f0-4472-a96a-d82de2f1d438": {},
- "9673e9a8-aa57-4461-9336-5fd3f0034e18": {},
- "9b4bfaea-dd1c-4add-b1de-e10f51c65fd3": {},
- "a4067d12-2fc0-4367-a213-9e4031cbc173": {},
- "aa8306d8-5417-43cc-b8e8-7e77b918682c": {},
- "b2326b8a-059d-48ca-96ac-8d8d5d841860": {},
- "b55f0c51-61a7-45c3-84df-33569b247796": {},
- "dc38a67a-f981-4e24-ba16-4443ada44484": {},
- },
- },
- "x-anchor-mailbox": {
- // The tenantID can be encoded in this parameter.
- // https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/95a63a7fe97d91b99979e5bf78e03f6acf40a286/msal/application.py#L185-L186
- // https://github.com/silverhack/monkey365/blob/b3f43c4a2d014fcc3aae0a4103c8f2610fbb4980/core/utils/Get-MonkeySecCompBackendUri.ps1#L70
- Input: ` User-Agent:
- - python-requests/2.31.0
- X-AnchorMailbox:
- - Oid:2b9b0cb5-d707-42e3-9504-d9b76ac7bec5@86843c34-863b-44d3-bb14-4f14e7c0564d
- x-client-current-telemetry:
- - 4|84,3|`,
- Expected: map[string]struct{}{
- "86843c34-863b-44d3-bb14-4f14e7c0564d": {},
- },
- },
-
- // Tenant onmicrosoft.com
- "onmicrosoft tenant": {
- Input: ` "oid": "7be15f3a-d9b5-4080-ba37-95aa2e3d244e",
- "platf": "3",
- "puid": "10032001170600C8",
- "scp": "Files.Read Files.Read.All Files.Read.Selected Files.ReadWrite Files.ReadWrite.All Files.ReadWrite.AppFolder Files.ReadWrite.Selected profile User.Export.All User.Invite.All User.ManageIdentities.All User.Read User.Read.All User.ReadBasic.All openid email",
- "signin_state": [
- "kmsi"
- ],
- "sub": "jIzit1WEdXqAH9KZXz-e-UcqsVa1pyPoh-2hw3xjEO4",
- "tenant_region_scope": "AS",
- "unique_name": "ben@xhoaxiuqng.onmicrosoft.com",
- "uti": "2Y26RWHsWEiqhD2vi_PFAg",
- "ver": "1.0",
- "wids": [
- "62e90394-69f5-4237-9190-012177145e10",
- "b79fbf4d-3ef9-4689-8143-76b194e85509"
- ],`,
- Expected: map[string]struct{}{
- "xhoaxiuqng.onmicrosoft.com": {},
- },
- },
-
- // Arbitrary test cases
- "spacing": {
- Input: `| Variable name | Description | Example value |
-| ----------------- | ------------------------------------------------------------- | ------------------------------------- |
-| AFASBaseUri | Base URI of the AFAS REST API endpoint for this environment | https://12345.rest.afas.online/ProfitRestServices |
-| AFASToke | App token in XML format for this environment | \\1\ \D5R324DD5F4TRD945E530ED3CDD70D94BBDEC4C732B43F285ECB12345678\ \ |
-| AADtenantID | Id of the Azure tenant | 12fc345b-0c67-4cde-8902-dabf2cad34b5 |
-| AADAppId | Id of the Azure app | f12345c6-7890-1f23-b456-789eb0bb1c23 |
-| AADAppSecret | Secret of the Azure app | G1X2HsBw-co3dTIB45RE6vY.mSU~6u.7.8 |`,
- Expected: map[string]struct{}{
- "12fc345b-0c67-4cde-8902-dabf2cad34b5": {},
- },
- },
- "newline": {
- Input: ` {\n \"mode\": \"Manual\"\n },\n \"bootstrapProfile\": {\n \"artifactSource\":
- \"Direct\"\n }\n },\n \"identity\": {\n \"type\": \"SystemAssigned\",\n
- \ \"principalId\":\"00000000-0000-0000-0000-000000000001\",\n \"tenantId\":
- \"d0a69dfd-9b9e-4833-9c33-c7903dd2e012\"\n },\n \"sku\": {\n \"name\": \"Base\",\n
- \ \"tier\": \"Free\"\n }\n}"
- headers:`,
- Expected: map[string]struct{}{
- "d0a69dfd-9b9e-4833-9c33-c7903dd2e012": {},
- },
- },
-
- // False positives
- "tid shouldn't match clientId": {
- Input: `"userId": "jdoe@businesscorp.ca", "isUserIdDisplayable": true, "isMRRT": true, "_clientId": "d3590ed6-52b3-4102-aeff-aad2292ab01c", }`,
- Expected: nil,
- },
- "tid shouldn't match subscription_id": {
- Input: `location = "eastus"
-subscription_id = "47ab1364-000d-4a53-838d-1537b1e3b49f"`,
- Expected: nil,
- },
- }
-
- runPatTest(t, cases, FindTenantIdMatches)
-}
-
-func Test_FindClientIdMatches(t *testing.T) {
- cases := map[string]testCase{
- "app": {
- Input: `var app = "4ba50db1-3f3f-4521-8a9a-1be0864d922a"`,
- Expected: map[string]struct{}{
- "4ba50db1-3f3f-4521-8a9a-1be0864d922a": {},
- },
- },
- "appid": {
- Input: `The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see https://aka.ms/azadsp-cli
-{
- "appId": "4ba50db1-3f3f-4521-8a9a-1be0864d922a",
- "displayName": "azure-cli-2022-12-02-15-40-24",`,
- Expected: map[string]struct{}{
- "4ba50db1-3f3f-4521-8a9a-1be0864d922a": {},
- },
- },
- "app_id": {
- Input: `msal:
- app_id: 'b9cbc91c-c890-4824-a487-91611bb0615a'`,
- Expected: map[string]struct{}{
- "b9cbc91c-c890-4824-a487-91611bb0615a": {},
- },
- },
- "application": {
- Input: `const application = \x60902aeb6d-29c7-4f6e-849d-4b933117e320\x60`,
- Expected: map[string]struct{}{
- "902aeb6d-29c7-4f6e-849d-4b933117e320": {},
- },
- },
- "applicationid": {
- Input: `# Login using Service Principal
-$ApplicationId = "1e002bca-c6e2-446e-a29e-a221909fe8aa"`,
- Expected: map[string]struct{}{
- "1e002bca-c6e2-446e-a29e-a221909fe8aa": {},
- },
- },
- "application id": {
- Input: `The application id is "029e3b51-60dd-47aa-81ad-3c15b389db86", you need to`,
- Expected: map[string]struct{}{
- "029e3b51-60dd-47aa-81ad-3c15b389db86": {},
- },
- },
- "application_id": {
- Input: ` credential:
- application_id: |
- bafe0126-03eb-4917-b3ff-4601c4e8f12f`,
- Expected: map[string]struct{}{
- "bafe0126-03eb-4917-b3ff-4601c4e8f12f": {},
- },
- },
- "application-id": {
- Input: `vcap.services.msal.application-id: 0704100e-7e76-4e62-bfb6-70bfd33906e2`,
- Expected: map[string]struct{}{
- "0704100e-7e76-4e62-bfb6-70bfd33906e2": {},
- },
- },
- "client": {
- Input: `String client = "902aeb6d-29c7-4f6e-849d-4b933117e320";`,
- Expected: map[string]struct{}{
- "902aeb6d-29c7-4f6e-849d-4b933117e320": {},
- },
- },
- "clientid": {
- Input: `export const msalConfig = {
- auth: {
- clientId: '82c54108-535c-40b2-87dc-2db599df3810',`,
- Expected: map[string]struct{}{
- "82c54108-535c-40b2-87dc-2db599df3810": {},
- },
- },
- "client id": {
- Input: `The client ID is: a54e584d-6fc4-464c-8479-dc67b5d87ab9`,
- Expected: map[string]struct{}{
- "a54e584d-6fc4-464c-8479-dc67b5d87ab9": {},
- },
- },
- "client_id": {
- Input: `location = "eastus"
-client_id = "89d5bd08-0d51-42cd-8eab-382c3ce11199"
-subscription_id = "47ab1364-000d-4a53-838d-1537b1e3b49f"
-`,
- Expected: map[string]struct{}{
- "89d5bd08-0d51-42cd-8eab-382c3ce11199": {},
- },
- },
- "client-id": {
- Input: `@TestPropertySource(properties = {
- "cas.authn.azure-active-directory.client-id=532c556b-1260-483f-9695-68d087fcd965",
- "cas.authn.azure-active-directory.client-secret`,
- Expected: map[string]struct{}{
- "532c556b-1260-483f-9695-68d087fcd965": {},
- },
- },
- "username": {
- Input: `az login --service-principal --username "21e144ac-532d-49ad-ba15-1c40694ce8b1" --password`,
- Expected: map[string]struct{}{
- "21e144ac-532d-49ad-ba15-1c40694ce8b1": {},
- },
- },
- "-u": {
- Input: `az login --service-principal -u "21e144ac-532d-49ad-ba15-1c40694ce8b1" -p`,
- Expected: map[string]struct{}{
- "21e144ac-532d-49ad-ba15-1c40694ce8b1": {},
- },
- },
-
- // Arbitrary test cases
- "spacing": {
- Input: `| Variable name | Description | Example value |
-| ----------------- | ------------------------------------------------------------- | ------------------------------------- |
-| AFASBaseUri | Base URI of the AFAS REST API endpoint for this environment | https://12345.rest.afas.online/ProfitRestServices |
-| AFASToke | App token in XML format for this environment | \\1\ \D5R324DD5F4TRD945E530ED3CDD70D94BBDEC4C732B43F285ECB12345678\ \ |
-| AADtenantID | Id of the Azure tenant | 12fc345b-0c67-4cde-8902-dabf2cad34b5 |
-| AADAppId | Id of the Azure app | f12345c6-7890-1f23-b456-789eb0bb1c23 |
-| AADAppSecret | Secret of the Azure app | G1X2HsBw-co3dTIB45RE6vY.mSU~6u.7.8 |`,
- Expected: map[string]struct{}{
- "f12345c6-7890-1f23-b456-789eb0bb1c23": {},
- },
- },
- }
-
- runPatTest(t, cases, FindClientIdMatches)
-}
diff --git a/pkg/detectors/azure_entra/refreshtoken/refreshtoken.go b/pkg/detectors/azure_entra/refreshtoken/refreshtoken.go
deleted file mode 100644
index 719bc3febd3b..000000000000
--- a/pkg/detectors/azure_entra/refreshtoken/refreshtoken.go
+++ /dev/null
@@ -1,322 +0,0 @@
-package refreshtoken
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strings"
-
- "github.com/golang-jwt/jwt/v5"
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ interface {
- detectors.Detector
- detectors.MaxSecretSizeProvider
- detectors.StartOffsetProvider
-} = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- refreshTokenPat = regexp.MustCompile(`\b[01]\.A[\w-]{50,}(?:\.\d)?\.Ag[\w-]{250,}(?:\.A[\w-]{200,})?`)
-)
-
-// 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{"0.A", "1.A"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureRefreshToken
-}
-
-func (s Scanner) Description() string {
- return "Azure Entra ID refresh tokens provide long-lasting access to an account."
-}
-
-func (Scanner) MaxSecretSize() int64 { return 2048 }
-
-func (Scanner) StartOffset() int64 { return 4096 }
-
-// FromData will find and optionally verify Azure RefreshToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- tokenMatches := findTokenMatches(dataStr)
- if len(tokenMatches) == 0 {
- return
- }
- clientMatches := azure_entra.FindClientIdMatches(dataStr)
- if len(clientMatches) == 0 {
- clientMatches[defaultClientId] = struct{}{}
- }
- tenantMatches := azure_entra.FindTenantIdMatches(dataStr)
- if len(tenantMatches) == 0 {
- tenantMatches[defaultTenantId] = struct{}{}
- }
-
- return s.processMatches(ctx, tokenMatches, clientMatches, tenantMatches, verify), err
-}
-
-func (s Scanner) processMatches(ctx context.Context, refreshTokens, clientIds, tenantIds map[string]struct{}, verify bool) (results []detectors.Result) {
- logCtx := logContext.AddLogger(ctx)
- invalidClientsForTenant := make(map[string]map[string]struct{})
- validTenants := make(map[string]struct{})
-
-TokenLoop:
- for token := range refreshTokens {
- var (
- r *detectors.Result
- clientId string
- tenantId string
- )
-
- ClientLoop:
- for cId := range clientIds {
- clientId = cId
- for tId := range tenantIds {
- tenantId = tId
-
- // Skip known invalid tenants.
- invalidClients := invalidClientsForTenant[tenantId]
- if invalidClients == nil {
- invalidClients = map[string]struct{}{}
- invalidClientsForTenant[tenantId] = invalidClients
- }
- if _, ok := invalidClients[clientId]; ok {
- continue
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- if _, ok := validTenants[tenantId]; !ok {
- if azure_entra.TenantExists(logCtx, client, tenantId) {
- validTenants[tenantId] = struct{}{}
- } else {
- delete(tenantIds, tenantId)
- continue
- }
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, token, clientId, tenantId)
- // Handle errors.
- if verificationErr != nil {
- if errors.Is(verificationErr, ErrTenantNotFound) {
- // Tenant doesn't exist. This shouldn't happen with the check above.
- delete(tenantIds, tenantId)
- continue
- } else if errors.Is(verificationErr, ErrClientNotFoundInTenant) {
- // Tenant is valid but the ClientID doesn't exist.
- invalidClients[clientId] = struct{}{}
- continue
- } else if errors.Is(verificationErr, ErrTokenExpired) {
- continue TokenLoop
- } else {
- // Received an unexpected/unhandled error type.
- r = createResult(token, clientId, tenantId, isVerified, extraData, verificationErr)
- break ClientLoop
- }
- }
-
- // The result is verified or there's only one associated client and tenant.
- if isVerified {
- r = createResult(token, clientId, tenantId, isVerified, extraData, verificationErr)
- break ClientLoop
- }
- }
- }
- }
-
- if r == nil {
- // Only include the clientId and tenantId if we're confident which one it is.
- if len(clientIds) != 1 || clientId == defaultClientId {
- clientId = ""
- }
- if len(tenantIds) != 1 || tenantId == defaultTenantId {
- tenantId = ""
- }
- r = createResult(token, clientId, tenantId, false, nil, nil)
- }
-
- results = append(results, *r)
- }
- return results
-}
-
-const defaultTenantId = "common"
-const defaultClientId = "d3590ed6-52b3-4102-aeff-aad2292ab01c" // Microsoft Office
-
-var (
- ErrTokenExpired = errors.New("token expired")
- ErrTenantNotFound = errors.New("tenant not found")
- ErrClientNotFoundInTenant = errors.New("application was not found in tenant")
-)
-
-// https://learn.microsoft.com/en-us/advertising/guides/authentication-oauth-get-tokens?view=bingads-13#refresh-accesstoken
-func verifyMatch(ctx context.Context, client *http.Client, refreshToken string, clientId string, tenantId string) (bool, map[string]string, error) {
- data := url.Values{}
- data.Set("client_id", clientId)
- data.Set("scope", "https://graph.microsoft.com/.default")
- data.Set("refresh_token", refreshToken)
- data.Set("grant_type", "refresh_token")
-
- tokenUrl := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantId)
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenUrl, bytes.NewBufferString(data.Encode()))
- if err != nil {
- return false, nil, err
- }
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- // Refresh token is valid.
- if res.StatusCode == http.StatusOK {
- var okResp successResponse
- if err := json.NewDecoder(res.Body).Decode(&okResp); err != nil {
- return false, nil, err
- }
-
- extraData := map[string]string{
- "Tenant": tenantId,
- "Client": clientId,
- "Scope": okResp.Scope,
- }
-
- // Add claims from the access token.
- token, _ := jwt.Parse(okResp.AccessToken, nil)
- if token != nil {
- claims := token.Claims.(jwt.MapClaims)
-
- if app := fmt.Sprint(claims["app_displayname"]); app != "" {
- extraData["Application"] = app
- }
-
- // The user information can be in a few claims.
- switch {
- case claims["email"] != nil:
- extraData["User"] = fmt.Sprint(claims["email"])
- case claims["upn"] != nil:
- extraData["User"] = fmt.Sprint(claims["upn"])
- case claims["unique_name"]:
- extraData["User"] = fmt.Sprint(claims["unique_name"])
- }
- }
- return true, extraData, nil
- }
-
- // Credentials *probably* aren't valid.
- var errResp errorResponse
- if err := json.NewDecoder(res.Body).Decode(&errResp); err != nil {
- return false, nil, err
- }
-
- switch res.StatusCode {
- case http.StatusBadRequest:
- // Error codes can be looked up by removing the `AADSTS` prefix.
- // https://login.microsoftonline.com/error?code=9002313
- d := errResp.Description
- switch {
- case strings.HasPrefix(d, "AADSTS70008:"),
- strings.HasPrefix(d, "AADSTS700082:"),
- strings.HasPrefix(d, "AADSTS70043:"):
- // https://login.microsoftonline.com/error?code=70008
- // https://login.microsoftonline.com/error?code=700082
- // https://login.microsoftonline.com/error?code=70043
- return false, nil, ErrTokenExpired
- case strings.HasPrefix(d, "AADSTS700016:"):
- // https://login.microsoftonline.com/error?code=700016
- return false, nil, ErrClientNotFoundInTenant
- case strings.HasPrefix(d, "AADSTS90002:"):
- // https://login.microsoftonline.com/error?code=90002
- return false, nil, ErrTenantNotFound
- case strings.HasPrefix(d, "AADSTS9002313:"):
- // This seems to be a generic "invalid token" error code.
- // 'invalid_grant': AADSTS9002313: Invalid request. Request is malformed or invalid.
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected error '%s': %s", errResp.Error, errResp.Description)
- }
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-type successResponse struct {
- Scope string `json:"scope"`
- AccessToken string `json:"access_token"`
-}
-
-type errorResponse struct {
- Error string `json:"error"`
- Description string `json:"error_description"`
-}
-
-// region Helper methods.
-func findTokenMatches(data string) map[string]struct{} {
- uniqueMatches := make(map[string]struct{})
- for _, match := range refreshTokenPat.FindAllStringSubmatch(data, -1) {
- m := match[0]
- if detectors.StringShannonEntropy(m) < 4 {
- continue
- }
- uniqueMatches[m] = struct{}{}
- }
- return uniqueMatches
-}
-
-func createResult(refreshToken, clientId, tenantId string, verified bool, extraData map[string]string, err error) *detectors.Result {
- r := &detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureRefreshToken,
- Raw: []byte(refreshToken),
- ExtraData: extraData,
- Verified: verified,
- }
- r.SetVerificationError(err, refreshToken)
-
- if clientId != "" && tenantId != "" {
- var sb strings.Builder
- sb.WriteString(`{`)
- sb.WriteString(`"refreshToken":"` + refreshToken + `"`)
- sb.WriteString(`,"clientId":"` + clientId + `"`)
- sb.WriteString(`,"tenantId":"` + tenantId + `"`)
- sb.WriteString(`}`)
- r.RawV2 = []byte(sb.String())
- }
-
- return r
-}
-
-// endregion
diff --git a/pkg/detectors/azure_entra/refreshtoken/refreshtoken_integration_test.go b/pkg/detectors/azure_entra/refreshtoken/refreshtoken_integration_test.go
deleted file mode 100644
index 066ef312b0be..000000000000
--- a/pkg/detectors/azure_entra/refreshtoken/refreshtoken_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package refreshtoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestRefreshToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AZUREREFRESHTOKEN")
- inactiveSecret := testSecrets.MustGetField("AZUREREFRESHTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurerefreshtoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureRefreshToken,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurerefreshtoken secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureRefreshToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurerefreshtoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureRefreshToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurerefreshtoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureRefreshToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Azurerefreshtoken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Azurerefreshtoken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_entra/refreshtoken/refreshtoken_test.go b/pkg/detectors/azure_entra/refreshtoken/refreshtoken_test.go
deleted file mode 100644
index ba3e5238773e..000000000000
--- a/pkg/detectors/azure_entra/refreshtoken/refreshtoken_test.go
+++ /dev/null
@@ -1,146 +0,0 @@
-package refreshtoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestRefreshToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- // Valid - 0.
- {
- name: "valid - token only",
- input: `"refresh_token": "0.AXEAFN5Pl6TDG0ibA8_OGCw6B-kFbFJoXnhBqmJD9wukrpZxAMc.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P9g0VCdz8smoWqJBpit_3P_ntszmbCH2-dGwpsamwQMbLl7QBa7tlfXH_NtpD1vNTGkacraUMyTM5lfg1AR1DLAxs-pNSpg8NfrHbNSRAIacCpOyqtU05Dg9l5LC7ZYwxT35dQWEK0EExLER-wxjW9DrDZNQV4J3Ktv1Z4ANT2N2rqAjPYqHTDPCCcOi980ptizeImgVYiVr37Ff0Hnr_lAi4Em0wGB7KDdu319sV9Sebe91FIRDs7GVvvv7GFvKjTeXJwHCpbhdqX4X2TRMryNrTNZ8QY7_Wa25MQm7v0qfFqDW_pRMxxohGhClSedZFnkzrreIhZ8ULJ9NCf8YENRHDP3LuOJP5gex-H0MUNsJQLxlDq3bH-i7Fz_cTEB3UN_bvgE9aNe-5gal-ykO_gSx-Kk5D-vZWpLDrFUdRSGYHmKr1zgEZvQjsFUj8pGWgUwssqN9SOPxTYIEzQaxPAul5AFKcxGYt2l4Kvhh58txUdayFAglWrkx1lrxnpIcjoRmHOo45AKlgH30bVOjjltwvD4L9SGMAHhni3F6mCB6aNLGpYCHjrbdsiWolHKV0leJmBYl2Ye4eosQf9YYdgPAbCQKqOJ6gfrxJJTcfrISqDVw1c6C9qPPdHbvdol_KfdJntyfuPpHovx7AfARBcjb6nMgYRBI0wFWsGuTNDcylicMFRcZx6v283wBv4U_0PrG1_Yd5ktfgaTVXF733C-ma_-s49tAvtDrJz2bmNFpotLyyQmwOiApLjeWFkH8EjBsBtpjhzzCIrOHuHR1I1gHChDMMDxfFT2k8dqxkvBpMLZ3zFWyJNl3LYbjgy9BkTIngvpQMSgRMl_VZ2eN_fZWk5wVOHjiUJJ9n4Y8IKQRM731vK_XEaK_BdtNLfC1Gw8hfLrIZpC6152zj6RhPn03gOK7G4RL6S21IfWKrw4kl6rdaPLgmMxlaI"`,
- want: []string{"0.AXEAFN5Pl6TDG0ibA8_OGCw6B-kFbFJoXnhBqmJD9wukrpZxAMc.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P9g0VCdz8smoWqJBpit_3P_ntszmbCH2-dGwpsamwQMbLl7QBa7tlfXH_NtpD1vNTGkacraUMyTM5lfg1AR1DLAxs-pNSpg8NfrHbNSRAIacCpOyqtU05Dg9l5LC7ZYwxT35dQWEK0EExLER-wxjW9DrDZNQV4J3Ktv1Z4ANT2N2rqAjPYqHTDPCCcOi980ptizeImgVYiVr37Ff0Hnr_lAi4Em0wGB7KDdu319sV9Sebe91FIRDs7GVvvv7GFvKjTeXJwHCpbhdqX4X2TRMryNrTNZ8QY7_Wa25MQm7v0qfFqDW_pRMxxohGhClSedZFnkzrreIhZ8ULJ9NCf8YENRHDP3LuOJP5gex-H0MUNsJQLxlDq3bH-i7Fz_cTEB3UN_bvgE9aNe-5gal-ykO_gSx-Kk5D-vZWpLDrFUdRSGYHmKr1zgEZvQjsFUj8pGWgUwssqN9SOPxTYIEzQaxPAul5AFKcxGYt2l4Kvhh58txUdayFAglWrkx1lrxnpIcjoRmHOo45AKlgH30bVOjjltwvD4L9SGMAHhni3F6mCB6aNLGpYCHjrbdsiWolHKV0leJmBYl2Ye4eosQf9YYdgPAbCQKqOJ6gfrxJJTcfrISqDVw1c6C9qPPdHbvdol_KfdJntyfuPpHovx7AfARBcjb6nMgYRBI0wFWsGuTNDcylicMFRcZx6v283wBv4U_0PrG1_Yd5ktfgaTVXF733C-ma_-s49tAvtDrJz2bmNFpotLyyQmwOiApLjeWFkH8EjBsBtpjhzzCIrOHuHR1I1gHChDMMDxfFT2k8dqxkvBpMLZ3zFWyJNl3LYbjgy9BkTIngvpQMSgRMl_VZ2eN_fZWk5wVOHjiUJJ9n4Y8IKQRM731vK_XEaK_BdtNLfC1Gw8hfLrIZpC6152zj6RhPn03gOK7G4RL6S21IfWKrw4kl6rdaPLgmMxlaI"},
- },
- {
- name: "valid - token+client+tenant",
- input: `
- {
- "tokenType": "Bearer",
- "expiresIn": 4742,
- "expiresOn": "2024-06-07 09:09:22.294640",
- "resource": "https://graph.windows.net",
- "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
- "refreshToken": "0.AUUAMe_N-B6jSkuT5F9XHpElWlj2JcxuFFnRLm_3awiSnuJQsa1.AgABAwEAAADnfolhJpSnRYB1SVj-Hgd8Agrf-wUA9P9oElBtlKe8a-5_1t2eEmBef50SCv8exOOrgjUFMLtPQj_XH1rq3Onj2dCFQaHzhm7DfoOxj5LH4kR9jPIbPf2yRI0CgxFLEGMf0biO9LxmvVwb_NKTScIc_MK4eBsXG-En_e3vaIJS5t-ghSvPAKzl3pxiYVvBdP1i_nUHPl4dsCkk9SKCexWnhi4tg9xVVIi-MIkGDJxThmuKfAko1VHMgx-tsHRKgPoXlJi51uNO0KQQUxnDnjiWmLapCe3hVtjfoINBlb3CpiHkfW5G9dzF4cmFOQJQG9RdW-CU6t4VmlamK9gSbNYfyd7fWr7Ebv9Bo06eWEwEBpQmJONJERNScnqMs5Ztba9kUHchXqJd9wZMH-NtWejuR92IqMmPoaY4DP52Yodu2hWZPv0pFEFsthPJ3YpViOaJnCoSQ7ba-qzVr8TnvFlkI8EfFKNbl47_WncwKXDrPk2FlZwG4ywX7s0dXYvXDJ-rMQHsDcJDMABQXrxaU0Z7ozCk_ftVgBQocWZHAkzBtWZNw9dS4ltux0GeAYekUjzE7UYrPw41DLWOLrr7V-kx5sZ6h66iiTi-zdsJ28LnRIX4aZ6IC7jxIG0FK-roPldOEjy0XJ-V6QmyjkEYT3PK23vUTHIz3EQ8JqGNJMJO5mWwbedlIl2xq-0CczybkR2MJgr4UAQKUBFMYuUYGWrVygte9d48usQ6-MhAavmkyZb5Mo_PeMnnNef-cl6c8RUzMAOpeiumFEG-gTzyDgaoM1eFjtYKTz0mr-0lPfrEavE4LfGXh87oDb0lNrbbkMNhAXjz2rJW8ex1REfeBH4oit0WeMWH-sIvpT3H8jsYIawfPp7rBN9z_TMX9AUbqROEY2Nv1jSJsXCX0sjLRweYiQnl-hHFfLcWwFIFjMfs7eOKSiOBKB3ZqjQw_A8OVDxhAQJybiVgW8U41IAjXGX0DNilrmE0PhDAqs5jQIBSO66G05yJj1RY3b2z8cYMG1lKAZ10IIDfo8f3FU-_m-w6zNVVkNZko89bX8tA91EjXpoUvmnPZKT84Qx9KvtRM561ABVEYnE152821Xy0HeObVue6M5WlF0puvqk1HnkfAUDxMk6qO1Xy7o0myTIV1R2yxFPpQX_pwCRB1IutSqz0s6E1XyfbRyv8TKxjX3_tGgvUy8KrZFeYJ9pRFsKIN_AJ9_a2GMG6h1b9aCIaA7jGlOkYlC-4LnhqoKxs4RpJJIpWWN6wZstGmIACwJS4",
- "familyName": "Doe",
- "givenName": "John",
- "identityProvider": "live.com",
- "tenantId": "16515984-9303-47f6-a59f-917611c8cb2b",
- "userId": "john.doe@outlook.com",
- "isUserIdDisplayable": true,
- "isMRRT": true,
- "_clientId": "1b730954-1685-4b74-9bfd-dac224a7b894",
- "_authority": "https://login.microsoftonline.com/16515984-9303-47f6-a59f-917611c8cb2b"
- }`,
- want: []string{`{"refreshToken":"0.AUUAMe_N-B6jSkuT5F9XHpElWlj2JcxuFFnRLm_3awiSnuJQsa1.AgABAwEAAADnfolhJpSnRYB1SVj-Hgd8Agrf-wUA9P9oElBtlKe8a-5_1t2eEmBef50SCv8exOOrgjUFMLtPQj_XH1rq3Onj2dCFQaHzhm7DfoOxj5LH4kR9jPIbPf2yRI0CgxFLEGMf0biO9LxmvVwb_NKTScIc_MK4eBsXG-En_e3vaIJS5t-ghSvPAKzl3pxiYVvBdP1i_nUHPl4dsCkk9SKCexWnhi4tg9xVVIi-MIkGDJxThmuKfAko1VHMgx-tsHRKgPoXlJi51uNO0KQQUxnDnjiWmLapCe3hVtjfoINBlb3CpiHkfW5G9dzF4cmFOQJQG9RdW-CU6t4VmlamK9gSbNYfyd7fWr7Ebv9Bo06eWEwEBpQmJONJERNScnqMs5Ztba9kUHchXqJd9wZMH-NtWejuR92IqMmPoaY4DP52Yodu2hWZPv0pFEFsthPJ3YpViOaJnCoSQ7ba-qzVr8TnvFlkI8EfFKNbl47_WncwKXDrPk2FlZwG4ywX7s0dXYvXDJ-rMQHsDcJDMABQXrxaU0Z7ozCk_ftVgBQocWZHAkzBtWZNw9dS4ltux0GeAYekUjzE7UYrPw41DLWOLrr7V-kx5sZ6h66iiTi-zdsJ28LnRIX4aZ6IC7jxIG0FK-roPldOEjy0XJ-V6QmyjkEYT3PK23vUTHIz3EQ8JqGNJMJO5mWwbedlIl2xq-0CczybkR2MJgr4UAQKUBFMYuUYGWrVygte9d48usQ6-MhAavmkyZb5Mo_PeMnnNef-cl6c8RUzMAOpeiumFEG-gTzyDgaoM1eFjtYKTz0mr-0lPfrEavE4LfGXh87oDb0lNrbbkMNhAXjz2rJW8ex1REfeBH4oit0WeMWH-sIvpT3H8jsYIawfPp7rBN9z_TMX9AUbqROEY2Nv1jSJsXCX0sjLRweYiQnl-hHFfLcWwFIFjMfs7eOKSiOBKB3ZqjQw_A8OVDxhAQJybiVgW8U41IAjXGX0DNilrmE0PhDAqs5jQIBSO66G05yJj1RY3b2z8cYMG1lKAZ10IIDfo8f3FU-_m-w6zNVVkNZko89bX8tA91EjXpoUvmnPZKT84Qx9KvtRM561ABVEYnE152821Xy0HeObVue6M5WlF0puvqk1HnkfAUDxMk6qO1Xy7o0myTIV1R2yxFPpQX_pwCRB1IutSqz0s6E1XyfbRyv8TKxjX3_tGgvUy8KrZFeYJ9pRFsKIN_AJ9_a2GMG6h1b9aCIaA7jGlOkYlC-4LnhqoKxs4RpJJIpWWN6wZstGmIACwJS4","clientId":"1b730954-1685-4b74-9bfd-dac224a7b894","tenantId":"16515984-9303-47f6-a59f-917611c8cb2b"}`},
- },
- {
- name: "valid - 0. in README",
- input: `
- ### Connection settings
-
- The connection settings are defined in the automation variables.
- 1. Create the following [user defined variables](https://docs.helloid.com/hc/en-us/articles/360014169933-How-to-Create-and-Manage-User-Defined-Variables)
-
- | Variable name | Description | Example value |
- | ----------------- | ------------------------------------------------------------- | ------------------------------------- |
- | AFASBaseUri | Base URI of the AFAS REST API endpoint for this environment | https://12345.rest.afas.online/ProfitRestServices |
- | AFASToke | App token in XML format for this environment | \\1\ \D5R324DD5F4TRD945E530ED3CDD70D94BBDEC4C732B43F285ECB12345678\ \ |
- | AADtenantID | Id of the Azure tenant | 12fc345b-0c67-4cde-8902-dabf2cad34b5 |
- | AADAppId | Id of the Azure app | f12345c6-7890-1f23-b456-789eb0bb1c23 |
- | AADRefreshToken | Refresh token of the Azure app | 0.ABCDEFGHIJKLMNOPQRS_PK0mtsE5afl5BYdPsASFbrS7jIZ0AAc.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P-XOTtPMo2xp9vfbHGvVkHaBZh4D3YmTkx_WagBOk358QjDwHUsiuVvyKvP6FTbQQt8kCidfMC9cmIYesHG4Ft2B1HwJNX28OpiFPuFti1D4Is30GgQ685i_ovS4iXDCUgtm2zpI6ZQJVqoOidXZQW_lSupdcclMK_JCIb7LBuJBDXfy0-f75C734_nxL0nggS9mn-e_KuJpHvypvU8OS9MPDBArhUopZum2y-2oNE65Wr-xpKm_Zeyr3iUGSZg98nbaryHw-lbeyFC8LcNqqMB_T7BcgvJicHSnj6DtjjpMyjKMwsCAnxz2bUYoLLjGFHk8EhDUCuV9lzUW1BTko5_I31TQdX0XY94vHTU34N93t3QPrQFMf8UhDjfQKiCDj3r2b7YR9ndS8MNp9MIa1CbL8vI4EM8GO4wtVI30Dhca4HaMtpph6uJp3echt-q7AVNQ_7ZHgx_YFZNqDmJyYq3nrae7LYRo0kvM382ss7JpCylodwya89mC_SlnrFhLM_zbt1TQkOtZqiVHbdQk3z-MX1iZso5Mk17Yks1ao0mS0RJfWVWSlOq_Sp-2yaiCsP-lV1PVdvvY_AkuOulP1kPG_VfC0DN3pGjSQJ8J9Ot5hfyElWyPst9Nc-ODErLhEqIl-3IR6wPKFN2ffjt8-dtCVMlVdBd1QANQOFBiIGA-_BZdGLvzROrWCOE9dDtyBQ_LnxdnnOVdjUqJ-xdql1p13Xjy6ZTtcZtTDmFN5hSMffYuUtuwEOy_Xb91Y2tvwOxcSe9dj7ElOLZDo2C7fGsMgaIJ1gK8xt9OWsS1o1sQZKQADTZq5TTxJp7PY3tJsUnOlD4q8ZEyVBQAvRKinpajBRcbq2lTCVt0JgXAryWztqYTpAxiqaBr51vuR4pbVRtKv-h_10tYD-TUV1WeX2fY3GuZA4B5g |
-
- ## contents`,
- want: []string{`{"refreshToken":"0.ABCDEFGHIJKLMNOPQRS_PK0mtsE5afl5BYdPsASFbrS7jIZ0AAc.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P-XOTtPMo2xp9vfbHGvVkHaBZh4D3YmTkx_WagBOk358QjDwHUsiuVvyKvP6FTbQQt8kCidfMC9cmIYesHG4Ft2B1HwJNX28OpiFPuFti1D4Is30GgQ685i_ovS4iXDCUgtm2zpI6ZQJVqoOidXZQW_lSupdcclMK_JCIb7LBuJBDXfy0-f75C734_nxL0nggS9mn-e_KuJpHvypvU8OS9MPDBArhUopZum2y-2oNE65Wr-xpKm_Zeyr3iUGSZg98nbaryHw-lbeyFC8LcNqqMB_T7BcgvJicHSnj6DtjjpMyjKMwsCAnxz2bUYoLLjGFHk8EhDUCuV9lzUW1BTko5_I31TQdX0XY94vHTU34N93t3QPrQFMf8UhDjfQKiCDj3r2b7YR9ndS8MNp9MIa1CbL8vI4EM8GO4wtVI30Dhca4HaMtpph6uJp3echt-q7AVNQ_7ZHgx_YFZNqDmJyYq3nrae7LYRo0kvM382ss7JpCylodwya89mC_SlnrFhLM_zbt1TQkOtZqiVHbdQk3z-MX1iZso5Mk17Yks1ao0mS0RJfWVWSlOq_Sp-2yaiCsP-lV1PVdvvY_AkuOulP1kPG_VfC0DN3pGjSQJ8J9Ot5hfyElWyPst9Nc-ODErLhEqIl-3IR6wPKFN2ffjt8-dtCVMlVdBd1QANQOFBiIGA-_BZdGLvzROrWCOE9dDtyBQ_LnxdnnOVdjUqJ-xdql1p13Xjy6ZTtcZtTDmFN5hSMffYuUtuwEOy_Xb91Y2tvwOxcSe9dj7ElOLZDo2C7fGsMgaIJ1gK8xt9OWsS1o1sQZKQADTZq5TTxJp7PY3tJsUnOlD4q8ZEyVBQAvRKinpajBRcbq2lTCVt0JgXAryWztqYTpAxiqaBr51vuR4pbVRtKv-h_10tYD-TUV1WeX2fY3GuZA4B5g","clientId":"f12345c6-7890-1f23-b456-789eb0bb1c23","tenantId":"12fc345b-0c67-4cde-8902-dabf2cad34b5"}`},
- },
-
- // Valid 1.
- {
- name: "valid - 1. token only",
- input: ` "refresh_token": "1.AVEAPn9m_nUaQ0iPPuqFsWAkYjIyPGgTIDFBgPvLEoUSLQVRAG5RAA.AgABAwEAAADW6jl31mB3T7ugrWTT8pFeAwDj_wUA9P8ZsxEzkXInsWHkCylMQMSSKto-NoegPmNj0uIemgAvxjnsDVGpC7sDRl4oEd51nQLQYowQYQ8aEcHh3nRrACc37UPYN-bwDte-tiwOEKuuGTOUrZft6YCqYiBoj7p3GZvKkIkUOGZvx7nydI1WoH9c7Z62NstZJ7ju_V38t5He6cKXEzNtlnrHpctxJX1uxxizdvwIR-_2VyMQjSSJS5lOS0Hi4Z_Nlthos5G-Gb-h9Y96fkkVm0D5E4xQh9avS7eCAPE2-N_guF3tmm7B4aqJg1lGnwv3WDWim14QhkF6Aji7juJUNmAExFyBaM7WnV_u3JnT-UNCz1p0O3AHa9d-dyDTUxQ8m_riB1HPoZZo6wPxg6txs6-fUE4LDR6tB5b43zwUl9XufcL4gKwnheLr8LvpJGjJn2tZUQzoU-ow4AZtJIxblfgYU_Zq0WOPJXltgAEw2JVoGsRy2jX8mXFZq1iCK5uEKBPXgrEfV-simUqI8GRZgXA1EnxG950MuaVfP3ZpsTYPGsvQgSzsUBKSy7cLd0p7UYtLub9UpX2PJxHrLQjACF-CSOMatVfSNzTErhSEmVWndpt87Yhova-XJUV48UxQ4ZZz26G6nOQ9qJ6db8ReAzBnok10e0eBuHR6K0OzcO54gjiQWPR4Tur7hD82KmYdOtShz234hDRGuS_b7mThfr_2ef9b2TQ9XYEV2QDUWiFYplfU0kOKA-wA7jOJGhXDkaJCIURxy53KuZPolXjTAy4",
- "expires_at": 1733138350.558087
- }`,
- want: []string{"1.AVEAPn9m_nUaQ0iPPuqFsWAkYjIyPGgTIDFBgPvLEoUSLQVRAG5RAA.AgABAwEAAADW6jl31mB3T7ugrWTT8pFeAwDj_wUA9P8ZsxEzkXInsWHkCylMQMSSKto-NoegPmNj0uIemgAvxjnsDVGpC7sDRl4oEd51nQLQYowQYQ8aEcHh3nRrACc37UPYN-bwDte-tiwOEKuuGTOUrZft6YCqYiBoj7p3GZvKkIkUOGZvx7nydI1WoH9c7Z62NstZJ7ju_V38t5He6cKXEzNtlnrHpctxJX1uxxizdvwIR-_2VyMQjSSJS5lOS0Hi4Z_Nlthos5G-Gb-h9Y96fkkVm0D5E4xQh9avS7eCAPE2-N_guF3tmm7B4aqJg1lGnwv3WDWim14QhkF6Aji7juJUNmAExFyBaM7WnV_u3JnT-UNCz1p0O3AHa9d-dyDTUxQ8m_riB1HPoZZo6wPxg6txs6-fUE4LDR6tB5b43zwUl9XufcL4gKwnheLr8LvpJGjJn2tZUQzoU-ow4AZtJIxblfgYU_Zq0WOPJXltgAEw2JVoGsRy2jX8mXFZq1iCK5uEKBPXgrEfV-simUqI8GRZgXA1EnxG950MuaVfP3ZpsTYPGsvQgSzsUBKSy7cLd0p7UYtLub9UpX2PJxHrLQjACF-CSOMatVfSNzTErhSEmVWndpt87Yhova-XJUV48UxQ4ZZz26G6nOQ9qJ6db8ReAzBnok10e0eBuHR6K0OzcO54gjiQWPR4Tur7hD82KmYdOtShz234hDRGuS_b7mThfr_2ef9b2TQ9XYEV2QDUWiFYplfU0kOKA-wA7jOJGhXDkaJCIURxy53KuZPolXjTAy4"},
- },
- {
- name: "valid - 1. tenant+client+token",
- input: `
- async function getAccessToken() {
- const tenantId = "31d1b7f4-4c4c-44cf-8d4e-b63e8512543e";
- const clientId = "16ed71fb-067e-47d9-b4bc-7656b14f1c5e";
- const clientSecret = ""; //para que funcione en sus ambientes tienen que poner el secreto,
- //si no lo tienen me lo piden y se los comparto por whatsapp,
- //lo tuvé que quitar porque no me dejaba hacer commit de los cambios en el repositorio
- const scope = "https://analysis.windows.net/powerbi/api/.default";
- let refresh_token = "1.AWEBqY9dsQppikubCN8WQsbFVyBfrV_ioNtAn7uoXAmQmkRiAUthAQ.AgABAwEAAADW6jl31mB3T7ugrWTT8pFeAwDs_yUA9P8OThUk9d3XIZbW4OGsJwHqqvjnVfcvEH4nejPU6R6-3onU34aSbVTEmxec0Nn3PaKfTBxucT-bu5XLSaTZSePKAAZw22RpqBb1w6ySb5GvvcCVpFU45mNfX5OH63y2Ryt-B7Beyp5yzlIgVgQA2S4OKhd_2qoVQoQXLApTwR78awwMFEQ7eVSbu5DO52dxisjB9ApHmpDCBip5y2MzyS7TizR31e-qBTnCMWt9RuHcKJySFFa-yPRBqYCgZLQWmEsKXBq-RIJToFsaGhVH2sXGXec0-Qsd9CvSPNFfGUDb_d2FLkZyKYKPra7Wmsvpw6qZJxO_TYprs1TbeWJYTTWT6WWI3xn10XtVml0a0P77ESqAWs-nbl6fS15mE24ZVU6rsuD7Q5AmtFfaddVN-JFP3fJ-6VsiY3KAefmdNULF_AVfMxAelBDSHtNllsMv4Qqs8N4h5bY4cabHibpu_OVA7WzfkNbxQ1dZpccZ9pi--xq5BCU3QAzereqYwmKretykB8twHw8Ryl5UVGocBNSJD65w2K3FJGZ6zbinfb_g1vV39iFxLdUz3JT1obce5ndeMBUeFmhN5XsczKAzTRK9c8aX6sdOd5pw5vUe-98qFRypPvCSF4hVA2ziwH38V9Dtc56UEVSMKISOacRMs8F_m9XtxP4X5KsWICIrK8_EXWfgmvEQnXm5PHV24ROsbnmmtUJWN1-vgzmNmSQ54_66W-fsCdnYAzDlwZeKr7wTZYO82nepNHX-wvTTEPV-QlrTPQFAlguP6nnxRc8MoxyiEvT4fOsDwD4yWFkLMMlKbyB6pQF_0CW_rQbyl0e6EKP2HbIDVKj628MDizjsdX693gplJevjF5g";
- `,
- want: []string{`{"refreshToken":"1.AWEBqY9dsQppikubCN8WQsbFVyBfrV_ioNtAn7uoXAmQmkRiAUthAQ.AgABAwEAAADW6jl31mB3T7ugrWTT8pFeAwDs_yUA9P8OThUk9d3XIZbW4OGsJwHqqvjnVfcvEH4nejPU6R6-3onU34aSbVTEmxec0Nn3PaKfTBxucT-bu5XLSaTZSePKAAZw22RpqBb1w6ySb5GvvcCVpFU45mNfX5OH63y2Ryt-B7Beyp5yzlIgVgQA2S4OKhd_2qoVQoQXLApTwR78awwMFEQ7eVSbu5DO52dxisjB9ApHmpDCBip5y2MzyS7TizR31e-qBTnCMWt9RuHcKJySFFa-yPRBqYCgZLQWmEsKXBq-RIJToFsaGhVH2sXGXec0-Qsd9CvSPNFfGUDb_d2FLkZyKYKPra7Wmsvpw6qZJxO_TYprs1TbeWJYTTWT6WWI3xn10XtVml0a0P77ESqAWs-nbl6fS15mE24ZVU6rsuD7Q5AmtFfaddVN-JFP3fJ-6VsiY3KAefmdNULF_AVfMxAelBDSHtNllsMv4Qqs8N4h5bY4cabHibpu_OVA7WzfkNbxQ1dZpccZ9pi--xq5BCU3QAzereqYwmKretykB8twHw8Ryl5UVGocBNSJD65w2K3FJGZ6zbinfb_g1vV39iFxLdUz3JT1obce5ndeMBUeFmhN5XsczKAzTRK9c8aX6sdOd5pw5vUe-98qFRypPvCSF4hVA2ziwH38V9Dtc56UEVSMKISOacRMs8F_m9XtxP4X5KsWICIrK8_EXWfgmvEQnXm5PHV24ROsbnmmtUJWN1-vgzmNmSQ54_66W-fsCdnYAzDlwZeKr7wTZYO82nepNHX-wvTTEPV-QlrTPQFAlguP6nnxRc8MoxyiEvT4fOsDwD4yWFkLMMlKbyB6pQF_0CW_rQbyl0e6EKP2HbIDVKj628MDizjsdX693gplJevjF5g","clientId":"16ed71fb-067e-47d9-b4bc-7656b14f1c5e","tenantId":"31d1b7f4-4c4c-44cf-8d4e-b63e8512543e"}`},
- },
- {
- name: "valid - 1. with more than 3 segments",
- input: `- request:
- body: client_id=04b07795-8ddb-461a-bbee-02f9e1bf7b46&grant_type=refresh_token&client_info=1&claims=%7B%22access_token%22%3A+%7B%22xms_cc%22%3A+%7B%22values%22%3A+%5B%22CP1%22%5D%7D%7D%7D&refresh_token=1.AAEA-W8xnNOnEke-ljgE71hsE5V3sATbjRpGu-4G-eG_e0YBAFIaAA.1.AgABAwEAAAAuQLDzsjJ3TYwhxABdnzRyAwDs_wUA7_9ENX2x1IM0b4hPzM-Ba_-qsHQqGxLKdo8wXF8BKQjnNc3wrqvP54z75uPEWb9uNOqw_Y8oxEQHggfkdIiq1NjPeA-A9jR2AI28nwlPd8dyuglTrUhLEKCKH0UFCeOi0lSxr7pefIa97LSJsDFKYPg1bCd9iuyRI5zQVGFbfHfq7gI8TSbpaVRSzNlsgftBrzIH_Zk55WCWz9ln8B-K1mc8gFDKsnclyvyCQU6e4CE0_6dHq1FXD-BwwV0yC1S9yyh673EHgY47s950p3Yqc7a8fOKY7iuwNKCDML51CUAZusRWfRYx0d1FXMI-JUfoBHTaZwsQyFePTlLjxkk2iEk4v9PTlTIvBdzZ6A8BVNDvpK_lBHgEpN_HVEbWM9ZHvWbeIU2_Lwt0SqLJEnq5GkTowX3aJe36JXWE6NBp5NJWS5-0EfEtl5iIWxtNG6u2E7lGAEbvUEAGXYa0abLxNwRiKvMNCKw01v42xIw1HqonNMT-tgY08KI3Icbyv-hzEwUwY8LYcjOGQTejDRe7CM9IogLe5flpK6m5aYKF8k4qVMN2PqCGCpofcqqyS448k9ATYx1Dm4-MAVsWScb22M106yIRSIbdo7tKdr3vBdNf0_FT0I-r20iDnUw_6sQc_Q8tR9uRuZbtrwD6IBAyYzqTG2KacAG6Gac-J5p-fsnPdjy0RmurvE149oA4G0KcAatNPmreiGzArXJEx7z20QwCgrh4j11j3dLJQMMafaxPdjHjPkwrG8Vz7xHVvRlfcn6x1d2Xhyq2VB6BwdZVIukbvxSg9Ci34qlKunOtohUxvisRRryV-w6MV1BomJz3W0QM0cTm5KVWpH9_0tQrioelqwvstQ6bOHRA3r7CzTZw0lfGMoDlaPubUqiy5t6P_b40hpkt40drKKHN972GwSDeR19cYiUFIONkc5APsV17tq0XZZgB8zpL-WilYK2SBQzescd4W1yXpFuh-uZ7bLAnQaa6xZzFDkN9-v4chZ2UAAvBsIURr7Q_8N_w2nH_.AQABFAEAAAAuQLDzsjJ3TYwhxABdnzRyNxO45BG1O4-twAhtMj2ZAGVMkIFTMaFoxpzzBJ7zB99xWtRIkmYAput3pQWfY44PP3WY0mRvEqSuLWlLa79Nz8jJANXNNTbPvXt8F_BDxeZUwb7gNax-q2Fr12Gb5YnTVnq9EUU9QEcuThPgC7tFWFu3_iwKjR-IMMcnQj6C7eh-ZcPMIn5Pkb3FkLwD7aZblol-4Z18pXV7dBOO8i0i4VZ5ud7tkxL5UjDZdbM8NrogAA&scope=https%3A%2F%2Fmanagement.core.windows.net%2F%2F.default+offline_access+openid+profile
- headers:`,
- want: []string{`1.AAEA-W8xnNOnEke-ljgE71hsE5V3sATbjRpGu-4G-eG_e0YBAFIaAA.1.AgABAwEAAAAuQLDzsjJ3TYwhxABdnzRyAwDs_wUA7_9ENX2x1IM0b4hPzM-Ba_-qsHQqGxLKdo8wXF8BKQjnNc3wrqvP54z75uPEWb9uNOqw_Y8oxEQHggfkdIiq1NjPeA-A9jR2AI28nwlPd8dyuglTrUhLEKCKH0UFCeOi0lSxr7pefIa97LSJsDFKYPg1bCd9iuyRI5zQVGFbfHfq7gI8TSbpaVRSzNlsgftBrzIH_Zk55WCWz9ln8B-K1mc8gFDKsnclyvyCQU6e4CE0_6dHq1FXD-BwwV0yC1S9yyh673EHgY47s950p3Yqc7a8fOKY7iuwNKCDML51CUAZusRWfRYx0d1FXMI-JUfoBHTaZwsQyFePTlLjxkk2iEk4v9PTlTIvBdzZ6A8BVNDvpK_lBHgEpN_HVEbWM9ZHvWbeIU2_Lwt0SqLJEnq5GkTowX3aJe36JXWE6NBp5NJWS5-0EfEtl5iIWxtNG6u2E7lGAEbvUEAGXYa0abLxNwRiKvMNCKw01v42xIw1HqonNMT-tgY08KI3Icbyv-hzEwUwY8LYcjOGQTejDRe7CM9IogLe5flpK6m5aYKF8k4qVMN2PqCGCpofcqqyS448k9ATYx1Dm4-MAVsWScb22M106yIRSIbdo7tKdr3vBdNf0_FT0I-r20iDnUw_6sQc_Q8tR9uRuZbtrwD6IBAyYzqTG2KacAG6Gac-J5p-fsnPdjy0RmurvE149oA4G0KcAatNPmreiGzArXJEx7z20QwCgrh4j11j3dLJQMMafaxPdjHjPkwrG8Vz7xHVvRlfcn6x1d2Xhyq2VB6BwdZVIukbvxSg9Ci34qlKunOtohUxvisRRryV-w6MV1BomJz3W0QM0cTm5KVWpH9_0tQrioelqwvstQ6bOHRA3r7CzTZw0lfGMoDlaPubUqiy5t6P_b40hpkt40drKKHN972GwSDeR19cYiUFIONkc5APsV17tq0XZZgB8zpL-WilYK2SBQzescd4W1yXpFuh-uZ7bLAnQaa6xZzFDkN9-v4chZ2UAAvBsIURr7Q_8N_w2nH_.AQABFAEAAAAuQLDzsjJ3TYwhxABdnzRyNxO45BG1O4-twAhtMj2ZAGVMkIFTMaFoxpzzBJ7zB99xWtRIkmYAput3pQWfY44PP3WY0mRvEqSuLWlLa79Nz8jJANXNNTbPvXt8F_BDxeZUwb7gNax-q2Fr12Gb5YnTVnq9EUU9QEcuThPgC7tFWFu3_iwKjR-IMMcnQj6C7eh-ZcPMIn5Pkb3FkLwD7aZblol-4Z18pXV7dBOO8i0i4VZ5ud7tkxL5UjDZdbM8NrogAA`},
- },
-
- // Invalid
- {
- name: "invalid - too short",
- input: `"refresh_token": "0.AXEAFN5Pl6TDG0ibA8_OGCw6B-kFbFJoXnhBqmJD9wukrpZxAMc.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P9g0VCdz8sm..."`,
- },
- {
- name: "invalid - low entropy",
- input: `"refresh_token": "0.Axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.Agxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"`,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_entra/serviceprincipal/sp.go b/pkg/detectors/azure_entra/serviceprincipal/sp.go
deleted file mode 100644
index edde95809f34..000000000000
--- a/pkg/detectors/azure_entra/serviceprincipal/sp.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package serviceprincipal
-
-import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strconv"
- "strings"
-
- "github.com/golang-jwt/jwt/v5"
-)
-
-var (
- Description = "Azure is a cloud service offering a wide range of services including compute, analytics, storage, and networking. Azure credentials can be used to access and manage these services."
- ErrConditionalAccessPolicy = errors.New("access blocked by Conditional Access policies (AADSTS53003)")
- ErrSecretInvalid = errors.New("invalid client secret provided")
- ErrSecretExpired = errors.New("the provided secret is expired")
- ErrTenantNotFound = errors.New("tenant not found")
- ErrClientNotFoundInTenant = errors.New("application was not found in tenant")
-)
-
-type TokenOkResponse struct {
- AccessToken string `json:"access_token"`
-}
-
-type TokenErrResponse struct {
- Error string `json:"error"`
- Description string `json:"error_description"`
-}
-
-// VerifyCredentials attempts to get a token using the provided client credentials.
-// See: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#get-a-token
-func VerifyCredentials(ctx context.Context, client *http.Client, tenantId string, clientId string, clientSecret string) (bool, map[string]string, error) {
- data := url.Values{}
- data.Set("client_id", clientId)
- data.Set("scope", "https://graph.microsoft.com/.default")
- data.Set("client_secret", clientSecret)
- data.Set("grant_type", "client_credentials")
-
- tokenUrl := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantId)
- encodedData := data.Encode()
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenUrl, strings.NewReader(encodedData))
- if err != nil {
- return false, nil, nil
- }
- req.Header.Set("Accept", "application/json")
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Set("Content-Length", strconv.Itoa(len(encodedData)))
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- // Credentials are valid.
- if res.StatusCode == http.StatusOK {
- var okResp TokenOkResponse
- if err := json.NewDecoder(res.Body).Decode(&okResp); err != nil {
- return false, nil, err
- }
-
- extraData := map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/azure/",
- "tenant": tenantId,
- "client": clientId,
- }
-
- // Add claims from the access token.
- if token, _ := jwt.Parse(okResp.AccessToken, nil); token != nil {
- claims := token.Claims.(jwt.MapClaims)
-
- if app := claims["app_displayname"]; app != nil {
- extraData["application"] = fmt.Sprint(app)
- }
- }
- return true, extraData, nil
- }
-
- // Credentials *probably* aren't valid.
- var errResp TokenErrResponse
- if err := json.NewDecoder(res.Body).Decode(&errResp); err != nil {
- return false, nil, err
- }
-
- switch res.StatusCode {
- case http.StatusBadRequest, http.StatusUnauthorized:
- // Error codes can be looked up by removing the `AADSTS` prefix.
- // https://login.microsoftonline.com/error?code=9002313
- // TODO: Handle AADSTS900382 (https://github.com/Azure/azure-sdk-for-js/issues/30557)
- d := errResp.Description
- switch {
- case strings.HasPrefix(d, "AADSTS53003:"):
- return false, nil, ErrConditionalAccessPolicy
- case strings.HasPrefix(d, "AADSTS700016:"):
- // https://login.microsoftonline.com/error?code=700016
- return false, nil, ErrClientNotFoundInTenant
- case strings.HasPrefix(d, "AADSTS7000215:"):
- // https://login.microsoftonline.com/error?code=7000215
- return false, nil, ErrSecretInvalid
- case strings.HasPrefix(d, "AADSTS7000222:"):
- // The secret has expired.
- // https://login.microsoftonline.com/error?code=7000222
- return false, nil, ErrSecretExpired
- case strings.HasPrefix(d, "AADSTS90002:"):
- // https://login.microsoftonline.com/error?code=90002
- return false, nil, ErrTenantNotFound
- default:
- return false, nil, fmt.Errorf("unexpected error '%s': %s", errResp.Error, errResp.Description)
- }
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
diff --git a/pkg/detectors/azure_entra/serviceprincipal/v1/spv1.go b/pkg/detectors/azure_entra/serviceprincipal/v1/spv1.go
deleted file mode 100644
index 8cced3f236a3..000000000000
--- a/pkg/detectors/azure_entra/serviceprincipal/v1/spv1.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package v1
-
-import (
- "context"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal"
- v2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ interface {
- detectors.Detector
- detectors.Versioner
-} = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // TODO: Azure storage access keys and investigate other types of creds.
- // https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#second-case-access-token-request-with-a-certificate
- // https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#third-case-access-token-request-with-a-federated-credential
- //clientSecretPat = regexp.MustCompile(`(?i)(?:secret|password| -p[ =]).{0,80}?([\w~@[\]:.?*/+=-]{31,34}`)
- // TODO: Tighten this regex and replace it with above.
- secretPat = regexp.MustCompile(`(?i)(?:secret|password| -p[ =]).{0,80}[^A-Za-z0-9!#$%&()*+,\-./:;<=>?@[\\\]^_{|}~]([A-Za-z0-9!#$%&()*+,\-./:;<=>?@[\\\]^_{|}~]{31,34})[^A-Za-z0-9!#$%&()*+,\-./:;<=>?@[\\\]^_{|}~]`)
-)
-
-func (s Scanner) Version() int {
- return 1
-}
-
-// 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{"azure", "az", "entra", "msal", "login.microsoftonline.com", ".onmicrosoft.com"}
-}
-
-// FromData will find and optionally verify Azure secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- clientSecrets := findSecretMatches(dataStr)
- if len(clientSecrets) == 0 {
- return
- }
- clientIds := azure_entra.FindClientIdMatches(dataStr)
- if len(clientIds) == 0 {
- return
- }
- tenantIds := azure_entra.FindTenantIdMatches(dataStr)
-
- client := s.client
- if client == nil {
- client = defaultClient
- }
- // The handling logic is identical for both versions.
- results = append(results, v2.ProcessData(ctx, clientSecrets, clientIds, tenantIds, verify, client)...)
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Azure
-}
-
-func (s Scanner) Description() string {
- return serviceprincipal.Description
-}
-
-func findSecretMatches(data string) map[string]struct{} {
- uniqueMatches := make(map[string]struct{})
- for _, match := range secretPat.FindAllStringSubmatch(data, -1) {
- m := match[1]
- // Ignore secrets that are handled by the V2 detector.
- if v2.SecretPat.MatchString(m) {
- continue
- }
- uniqueMatches[m] = struct{}{}
- }
- return uniqueMatches
-}
diff --git a/pkg/detectors/azure_entra/serviceprincipal/v1/spv1_integration_test.go b/pkg/detectors/azure_entra/serviceprincipal/v1/spv1_integration_test.go
deleted file mode 100644
index f329edda3a78..000000000000
--- a/pkg/detectors/azure_entra/serviceprincipal/v1/spv1_integration_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package v1
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzure_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AZURE_SECRET")
- secretInactive := testSecrets.MustGetField("AZURE_INACTIVE")
- id := testSecrets.MustGetField("AZURE_ID")
- tenantId := testSecrets.MustGetField("AZURE_TENANT_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf(`
- tenant_id=%s
- client_id=%s
- client_secret=%s
- client_secret=%s
- `, tenantId, id, secretInactive, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Azure,
- Redacted: id,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf(`
- tenant_id=%s
- client_id=%s
- client_secret=%s
- `, tenantId, id, secretInactive)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Azure,
- Redacted: id,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Azure.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Azure.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_entra/serviceprincipal/v1/spv1_test.go b/pkg/detectors/azure_entra/serviceprincipal/v1/spv1_test.go
deleted file mode 100644
index 12fd35a1506d..000000000000
--- a/pkg/detectors/azure_entra/serviceprincipal/v1/spv1_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package v1
-
-import (
- "testing"
-
- "github.com/google/go-cmp/cmp"
-)
-
-type testCase struct {
- Input string
- Expected map[string]struct{}
-}
-
-func Test_FindClientSecretMatches(t *testing.T) {
- cases := map[string]testCase{
- "client_secret": {
- Input: ` "TenantId": "3d7e0652-b03d-4ed2-bf86-f1299cecde17",
- "ClientSecret": "gHduiL_j6t4b6DG?Qr-G6M@IOS?mX3B9",`,
- Expected: map[string]struct{}{"gHduiL_j6t4b6DG?Qr-G6M@IOS?mX3B9": {}},
- },
- "client_secret1": {
- Input: ` public static string clientId = "413ff05b-6d54-41a7-9271-9f964bc10624";
- public static string clientSecret = "k72~odcN_6TbVh5D~19_1Qkj~87trteArL";
-
- private const string `,
- Expected: map[string]struct{}{"k72~odcN_6TbVh5D~19_1Qkj~87trteArL": {}},
- },
- "client_secret2": {
- Input: ` "azClientSecret": "2bWD_tu3~9B0_.R0W3BFJN-Hu_xjfR8EL5",
- "kvVaultUri": "https://corp.vault.azure.net/",`,
- Expected: map[string]struct{}{"2bWD_tu3~9B0_.R0W3BFJN-Hu_xjfR8EL5": {}},
- },
- "client_secret3": {
- Input: `# COMMAND ----------
-
-clientID = "193e3d24-8d04-404c-95a9-074efaa83147"
-tenantID = "28241a04-7ac0-44f1-a996-84dc181f9861"
-secret = "a2djRWTXDS1iMbThoK.C7e:yVsUdL3[:"`,
- Expected: map[string]struct{}{"a2djRWTXDS1iMbThoK.C7e:yVsUdL3[:": {}},
- },
- "client_secret4": {
- Input: `tenantID = "9f37a392-g0ae-1280-9796-f1864210effc"
-secret = "s.1_56k~5jmRDm23y.dTg5_XjTAcRjCbH."
-
-# COMMAND ----------
-
-configs = {"fs.azure.account.auth.type": "OAuth"`,
- Expected: map[string]struct{}{"s.1_56k~5jmRDm23y.dTg5_XjTAcRjCbH.": {}},
- },
- "client_secret5": {
- Input: `public class HardcodedAzureCredentials {
- private final String clientId = "81734019-15a3-50t8-3253-5abe78abc3a1";
- private final String username = "username@example.onmicrosoft.com";
- private final String clientSecret = "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_";`,
- Expected: map[string]struct{}{"1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_": {}},
- },
- // https://github.com/kedacore/keda/blob/main/pkg/scalers/azure_log_analytics_scaler_test.go
- "client_secret6": {
- Input: `const (
- tenantID = "d248da64-0e1e-4f79-b8c6-72ab7aa055eb"
- clientID = "41826dd4-9e0a-4357-a5bd-a88ad771ea7d"
- clientSecret = "U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs"
- workspaceID = "074dd9f8-c368-4220-9400-acb6e80fc325"`,
- Expected: map[string]struct{}{"U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs": {}},
- },
- "client_secret7": {
- Input: ` "AZUREAD-AKS-APPID-SECRET": "xW25Gt-Mf0.ue3jFqE68jtFqtt-4L_8R51",
- "AZUREAD-AKS-TENANTID": "d3a761f8-e7ea-473a-b907-1e7b3ef92aa9",`,
- Expected: map[string]struct{}{"xW25Gt-Mf0.ue3jFqE68jtFqtt-4L_8R51": {}},
- },
- "client_secret8": {
- Input: ` "AZUREAD-AKS-APPID-SECRET": "8w__IGsaY.6g6jUxb1.pPGK262._pgX.q-",`,
- Expected: map[string]struct{}{"8w__IGsaY.6g6jUxb1.pPGK262._pgX.q-": {}},
- },
- // "client_secret6": {
- // Input: ``,
- // Expected: map[string]struct{}{"": {}},
- // },
-
- "password": {
- Input: `# Login using Service Principal
-$ApplicationId = "5cec5dfb-0ac4-4938-b477-3f9638881b93"
-$SecuredPassword = ConvertTo-SecureString -String "gHduiL_j6t4b6DG?Qr-G6M@IOS?mX3B9" -AsPlainText -Force
-$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $ApplicationId, $SecuredPassword`,
- Expected: map[string]struct{}{"gHduiL_j6t4b6DG?Qr-G6M@IOS?mX3B9": {}},
- },
-
- // False positives
- "placeholder_secret": {
- Input: `- Log in with a service principal using a client secret:
-
-az login --service-principal --username {{http://azure-cli-service-principal}} --password {{secret}} --tenant {{someone.onmicrosoft.com}}`,
- Expected: nil,
- },
- // "client_secret3": {
- // Input: ``,
- // Expected: map[string]struct{}{
- // "": {},
- // },
- // },
- }
-
- for name, test := range cases {
- t.Run(name, func(t *testing.T) {
- matches := findSecretMatches(test.Input)
- if len(matches) == 0 {
- if len(test.Expected) != 0 {
- t.Fatalf("no matches found, expected: %v", test.Expected)
- return
- } else {
- return
- }
- }
-
- if diff := cmp.Diff(test.Expected, matches); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_entra/serviceprincipal/v2/spv2.go b/pkg/detectors/azure_entra/serviceprincipal/v2/spv2.go
deleted file mode 100644
index 3b09b0fca0f4..000000000000
--- a/pkg/detectors/azure_entra/serviceprincipal/v2/spv2.go
+++ /dev/null
@@ -1,187 +0,0 @@
-package v2
-
-import (
- "context"
- "errors"
- "net/http"
- "regexp"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ interface {
- detectors.Detector
- detectors.Versioner
-} = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- SecretPat = regexp.MustCompile(`(?:[^a-zA-Z0-9_~.-]|\A)([a-zA-Z0-9_~.-]{3}\dQ~[a-zA-Z0-9_~.-]{31,34})(?:[^a-zA-Z0-9_~.-]|\z)`)
-)
-
-func (s Scanner) Version() int {
- return 2
-}
-
-// 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{"q~"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Azure
-}
-
-func (s Scanner) Description() string {
- return serviceprincipal.Description
-}
-
-// FromData will find and optionally verify Azure secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- clientSecrets := findSecretMatches(dataStr)
- if len(clientSecrets) == 0 {
- return results, nil
- }
- clientIds := azure_entra.FindClientIdMatches(dataStr)
- tenantIds := azure_entra.FindTenantIdMatches(dataStr)
-
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- results = append(results, ProcessData(ctx, clientSecrets, clientIds, tenantIds, verify, client)...)
- return results, nil
-}
-
-func ProcessData(ctx context.Context, clientSecrets, clientIds, tenantIds map[string]struct{}, verify bool, client *http.Client) (results []detectors.Result) {
- logCtx := logContext.AddLogger(ctx)
- invalidClientsForTenant := make(map[string]map[string]struct{})
-
-SecretLoop:
- for clientSecret := range clientSecrets {
- var (
- r *detectors.Result
- clientId string
- tenantId string
- )
-
- ClientLoop:
- for cId := range clientIds {
- clientId = cId
- for tId := range tenantIds {
- tenantId = tId
-
- // Skip known invalid tenants.
- invalidClients := invalidClientsForTenant[tenantId]
- if invalidClients == nil {
- invalidClients = map[string]struct{}{}
- invalidClientsForTenant[tenantId] = invalidClients
- }
- if _, ok := invalidClients[clientId]; ok {
- continue
- }
-
- if verify {
- if !azure_entra.TenantExists(logCtx, client, tenantId) {
- // Tenant doesn't exist
- delete(tenantIds, tenantId)
- continue
- }
- // Tenant exists, ensure this isn't attempted as a clientId.
- delete(clientIds, tenantId)
-
- isVerified, extraData, verificationErr := serviceprincipal.VerifyCredentials(ctx, client, tenantId, clientId, clientSecret)
- // Handle errors.
- if verificationErr != nil {
- switch {
- case errors.Is(verificationErr, serviceprincipal.ErrConditionalAccessPolicy):
- // Do nothing.
- case errors.Is(verificationErr, serviceprincipal.ErrSecretInvalid):
- continue ClientLoop
- case errors.Is(verificationErr, serviceprincipal.ErrSecretExpired):
- continue SecretLoop
- case errors.Is(verificationErr, serviceprincipal.ErrTenantNotFound):
- // Tenant doesn't exist. This shouldn't happen with the check above.
- delete(tenantIds, tenantId)
- continue
- case errors.Is(verificationErr, serviceprincipal.ErrClientNotFoundInTenant):
- // Tenant is valid but the ClientID doesn't exist.
- invalidClients[clientId] = struct{}{}
- continue
- }
- }
-
- // The result is verified or there's only one associated client and tenant.
- if isVerified || (len(clientIds) == 1 && len(tenantIds) == 1) {
- r = createResult(tenantId, clientId, clientSecret, isVerified, extraData, verificationErr)
- break ClientLoop
- }
- }
- }
- }
-
- if r == nil {
- // Only include the clientId and tenantId if we're confident which one it is.
- if len(clientIds) != 1 {
- clientId = ""
- }
- if len(tenantIds) != 1 {
- tenantId = ""
- }
- r = createResult(tenantId, clientId, clientSecret, false, nil, nil)
- }
-
- results = append(results, *r)
- }
- return results
-}
-
-func createResult(tenantId string, clientId string, clientSecret string, verified bool, extraData map[string]string, err error) *detectors.Result {
- r := &detectors.Result{
- DetectorType: detectorspb.DetectorType_Azure,
- Raw: []byte(clientSecret),
- ExtraData: extraData,
- Verified: verified,
- Redacted: clientSecret[:5] + "...",
- }
- r.SetVerificationError(err, clientSecret)
-
- // Tenant ID is required for verification, but it may not always be present.
- // e.g., ACR or Azure SQL use client id+secret without tenant.
- if clientId != "" && tenantId != "" {
- var sb strings.Builder
- sb.WriteString(`{`)
- sb.WriteString(`"clientSecret":"` + clientSecret + `"`)
- sb.WriteString(`,"clientId":"` + clientId + `"`)
- sb.WriteString(`,"tenantId":"` + tenantId + `"`)
- sb.WriteString(`}`)
- r.RawV2 = []byte(sb.String())
- }
-
- return r
-}
-
-func findSecretMatches(data string) map[string]struct{} {
- uniqueMatches := make(map[string]struct{})
- for _, match := range SecretPat.FindAllStringSubmatch(data, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
- return uniqueMatches
-}
diff --git a/pkg/detectors/azure_entra/serviceprincipal/v2/spv2_integration_test.go b/pkg/detectors/azure_entra/serviceprincipal/v2/spv2_integration_test.go
deleted file mode 100644
index 704f4ee5ac2e..000000000000
--- a/pkg/detectors/azure_entra/serviceprincipal/v2/spv2_integration_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package v2
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzure_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AZURE_SECRET")
- secretInactive := testSecrets.MustGetField("AZURE_INACTIVE")
- id := testSecrets.MustGetField("AZURE_ID")
- tenantId := testSecrets.MustGetField("AZURE_TENANT_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf(`
- tenant_id=%s
- client_id=%s
- client_secret=%s
- client_secret=%s
- `, tenantId, id, secretInactive, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Azure,
- Redacted: id,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf(`
- tenant_id=%s
- client_id=%s
- client_secret=%s
- `, tenantId, id, secretInactive)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Azure,
- Redacted: id,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Azure.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Azure.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_entra/serviceprincipal/v2/spv2_test.go b/pkg/detectors/azure_entra/serviceprincipal/v2/spv2_test.go
deleted file mode 100644
index 68eb128368b1..000000000000
--- a/pkg/detectors/azure_entra/serviceprincipal/v2/spv2_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package v2
-
-import (
- "testing"
-
- "github.com/google/go-cmp/cmp"
-)
-
-type testCase struct {
- Input string
- Expected map[string]struct{}
-}
-
-func Test_FindClientSecretMatches(t *testing.T) {
- cases := map[string]testCase{
- "secret": {
- Input: `servicePrincipal:
- tenantId: "608e4ac4-2ca8-40dd-a046-4064540a1cde"
- clientId: "1474bfe8-663c-486e-9daf-f1f580302218"
- clientSecret: "R028Q~ZOKzgCYyhr1ZJNNKhP8gUcD3Dpy2jMqaXf"
-agentImage: "karbar.azurecr.io/kar-agent"`,
- Expected: map[string]struct{}{
- "R028Q~ZOKzgCYyhr1ZJNNKhP8gUcD3Dpy2jMqaXf": {},
- },
- },
- "secret_start_with_dash": {
- Input: `azure:
- active-directory:
- enabled: true
- profile:
- tenant-id: 11111111-1111-1111-1111-111111111111
- credential:
- client-id: 00000000-0000-0000-0000-000000000000
- client-secret: -bs8Q~F9mPSWiDihY0NIpcQjAWoUoQ.c-seM-c0_`,
- Expected: map[string]struct{}{
- "-bs8Q~F9mPSWiDihY0NIpcQjAWoUoQ.c-seM-c0_": {},
- },
- },
- "secret_end_with_dash": {
- Input: `OPENID_CLIENT_ID=8595f61a-109a-497d-8c8f-566b733e95fe
-OPENID_CLIENT_SECRET=aZ78Q~C~--E4dgsHZklBWtAw0mdajUHAaXXG5cq-
-OPENID_GRANT_TYPE=client_credentials`,
- Expected: map[string]struct{}{
- "aZ78Q~C~--E4dgsHZklBWtAw0mdajUHAaXXG5cq-": {},
- },
- },
- "client_secret": {
- Input: ` "RequestBody": "client_id=4cb7565b-9ff0-49ed-b317-4dace4a70396\u0026grant_type=client_credentials\u0026client_info=1\u0026client_secret=-6s8Q~.Q9CKMOXHGs_BA3ig2wUzyDRyulhWEOc3u\u0026claims=%7B%22access_token%22%3A\u002B%7B%22xms_cc%22%3A\u002B%7B%22values%22%3A\u002B%5B%22CP1%22%5D%7D%7D%7D\u0026scope=https%3A%2F%2Fmanagement.azure.com%2F.default",`,
- Expected: map[string]struct{}{
- "-6s8Q~.Q9CKMOXHGs_BA3ig2wUzyDRyulhWEOc3u": {},
- },
- },
- }
-
- for name, test := range cases {
- t.Run(name, func(t *testing.T) {
- matches := findSecretMatches(test.Input)
- if len(matches) == 0 {
- if len(test.Expected) != 0 {
- t.Fatalf("no matches found, expected: %v", test.Expected)
- return
- } else {
- return
- }
- }
-
- if diff := cmp.Diff(test.Expected, matches); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_openai/azure_openai.go b/pkg/detectors/azure_openai/azure_openai.go
deleted file mode 100644
index cb8f78224e5b..000000000000
--- a/pkg/detectors/azure_openai/azure_openai.go
+++ /dev/null
@@ -1,172 +0,0 @@
-package azure_openai
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
-)
-
-// Scanner detects API keys for Azure's OpenAI service.
-// https://learn.microsoft.com/en-us/azure/ai-services/openai/reference
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- // TODO: Investigate custom `azure-api.net` endpoints.
- // https://github.com/openai/openai-python#microsoft-azure-openai
- azureUrlPat = regexp.MustCompile(`(?i)([a-z0-9-]+\.openai\.azure\.com)`)
- azureKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"api[_.-]?key", "openai[_.-]?key"}) + `\b(?-i:([a-f0-9]{32}))\b`)
-
- invalidServices = simple.NewCache[struct{}]()
-)
-
-// 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{".openai.azure.com"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureOpenAI
-}
-
-func (s Scanner) Description() string {
- return "Azure OpenAI provides various AI models and services. The API keys can be used to access and interact with these models and services."
-}
-
-// FromData will find and optionally verify OpenAI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- // De-duplicate results.
- tokens := make(map[string]struct{})
- for _, match := range azureKeyPat.FindAllStringSubmatch(dataStr, -1) {
- tokens[match[1]] = struct{}{}
- }
- if len(tokens) == 0 {
- return
- }
- urls := make(map[string]struct{})
- for _, match := range azureUrlPat.FindAllStringSubmatch(dataStr, -1) {
- u := match[1]
- if invalidServices.Exists(u) {
- continue
- }
- urls[u] = struct{}{}
- }
-
- // Process results.
- logCtx := logContext.AddLogger(ctx)
- for token := range tokens {
- s1 := detectors.Result{
- DetectorType: s.Type(),
- Redacted: token[:3] + "..." + token[25:],
- Raw: []byte(token),
- }
-
- for url := range urls {
- if verify {
- client := s.client
- if client == nil {
- client = common.SaneHttpClient()
- }
-
- isVerified, extraData, verificationErr := verifyAzureToken(logCtx, client, url, token)
- if isVerified || len(urls) == 1 {
- s1.RawV2 = []byte(token + ":" + url)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, token)
- break
- }
-
- // Instance doesn't exist.
- // Verification issue: lookup azsdk-east-us.openai.azure.com: no such host
- if verificationErr != nil && strings.Contains(verificationErr.Error(), "no such host") {
- delete(urls, url)
- invalidServices.Set(url, struct{}{})
- }
- }
- }
-
- results = append(results, s1)
- }
- return
-}
-
-func verifyAzureToken(ctx logContext.Context, client *http.Client, baseUrl, token string) (bool, map[string]string, error) {
- // TODO: Replace this with a more suitable long-term endpoint.
- // Most endpoints require additional info, e.g., deployment name, which complicates verification.
- // This may be retired in the future, so we should look for another candidate.
- // https://learn.microsoft.com/en-us/answers/questions/1371786/get-azure-openai-deployments-in-api
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://%s/openai/deployments?api-version=2023-03-15-preview", baseUrl), nil)
- if err != nil {
- return false, nil, nil
- }
-
- req.Header.Set("Api-Key", token)
- req.Header.Set("Content-Type", "application/json")
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- body, err := io.ReadAll(res.Body)
- if err != nil {
- return false, nil, err
- }
-
- var deployments deploymentsResponse
- if err := json.Unmarshal(body, &deployments); err != nil {
- if json.Valid(body) {
- return false, nil, fmt.Errorf("failed to decode response %s: %w", req.URL, err)
- } else {
- // If the response isn't JSON it's highly unlikely to be valid.
- return false, nil, nil
- }
- }
-
- // JSON unmarshal doesn't check whether the structure actually matches.
- if deployments.Object == "" {
- return false, nil, nil
- }
-
- // No extra data available at the moment.
- return true, nil, nil
- case http.StatusUnauthorized:
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected response status %d for %s", res.StatusCode, req.URL)
- }
-}
-
-type deploymentsResponse struct {
- Data []deployment `json:"data"`
- Object string `json:"object"`
-}
-
-type deployment struct {
- ID string `json:"id"`
-}
diff --git a/pkg/detectors/azure_openai/azure_openai_integration_test.go b/pkg/detectors/azure_openai/azure_openai_integration_test.go
deleted file mode 100644
index 8e0aa7fd56b9..000000000000
--- a/pkg/detectors/azure_openai/azure_openai_integration_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azure_openai
-
-import (
- "context"
- "fmt"
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- "testing"
- "time"
-)
-
-func TestAzureOpenAI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AZUREOPENAI")
- inactiveSecret := testSecrets.MustGetField("AZUREOPENAI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azureopenai secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureOpenAI,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azureopenai secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureOpenAI,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azureopenai secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureOpenAI,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azureopenai secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureOpenAI,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Azureopenai.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Azureopenai.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_openai/azure_openai_test.go b/pkg/detectors/azure_openai/azure_openai_test.go
deleted file mode 100644
index 97ab253761d1..000000000000
--- a/pkg/detectors/azure_openai/azure_openai_test.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package azure_openai
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureOpenAI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "Generic environment variables",
- input: `export OPENAI_API_VERSION=2023-07-15-preview
- export OPENAI_API_TYPE=AZURE
- export OPENAI_API_BASE=https://james-test-gpt4.openai.azure.com/
- export OPENAI_API_KEY=3397348fcdcb4a5fbeb6cceb5a6a284f`,
- want: []string{"3397348fcdcb4a5fbeb6cceb5a6a284f"},
- },
- {
- name: "Generic non-structured",
- input: `# {'input': ['This is a test query.'], 'engine': 'text-embedding-ada-002'}
- # url /openai/deployments/text-embedding-ada-002/embeddings?api-version=2022-12-01
- # params {'input': ['This is a test query.'], 'encoding_format': 'base64'}
- # headers None
- # message='Request to OpenAI API' method=post path=https://notebook-openai01.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2022-12-01
- # api_version=2022-12-01 data='{"input": ["This is a test query."], "encoding_format": "base64"}' message='Post details'
- # https://notebook-openai01.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2022-12-01
- # {'X-OpenAI-Client-User-Agent': '{"bindings_version": "0.27.6", "httplib": "requests", "lang": "python", "lang_version": "3.11.2", "platform": "macOS-13.2-arm64-arm-64bit",
- "publisher": "openai", "uname": "Darwin 22.3.0 Darwin Kernel Version 22.3.0: Thu Jan 5 20:48:54 PST 2023; root:xnu-8792.81.2~2/RELEASE_ARM64_T6000 arm64 arm"}', 'User-Agent': 'OpenAI/v1 PythonBindings/0.27.6', 'api-key': '49eb7c2d3acd41f4ac31fef59ceacbba', 'OpenAI-Debug': 'true', 'Content-Type': 'application/json'}`,
- want: []string{"49eb7c2d3acd41f4ac31fef59ceacbba"},
- },
- {
- name: "Python",
- input: `import openai
-
- openai.api_key = '1bb7dff73fe449de829363ea03bab134'
- openai.api_base = "https://hrcop-openai.openai.azure.com/"
- `,
- want: []string{"1bb7dff73fe449de829363ea03bab134"},
- },
- {
- name: "Python environment variables",
- input: `os.environ["OPENAI_API_TYPE"] = "azure"
- os.environ["OPENAI_API_VERSION"] = "2023-03-15-preview"
- os.environ["OPENAI_API_BASE"] = "https://superhackathonai101-openai.openai.azure.com/"
- os.environ["OPENAI_API_KEY"] = '1bb7dde73fe449de229361ea03bab234'`,
- want: []string{"1bb7dde73fe449de229361ea03bab234"},
- },
- {
- name: "TypeScript",
- input: `import OpenAI from "openai";
- export const openai = new OpenAI({
- apiKey: "3375e3ad9a874cd6bd954b6f163be84f",
- baseURL:
- "https://kumar-azure.openai.azure.com/openai/deployments/ChatAutoUpdate",
- defaultQuery: { "api-version": "2023-06-01-preview" },
- });`,
- want: []string{"3375e3ad9a874cd6bd954b6f163be84f"},
- },
- {
- name: "OpenAi key name",
- input: `{
- "IsEncrypted": false,
- "Values": {
- "AZURE_OPENAI_ENDPOINT": "https://bcdemo-openai.openai.azure.com/",
- "AZURE_OPENAI_KEY": "57d2de35873840b5ad59d742e90e974e"
- }
- }`,
- want: []string{"57d2de35873840b5ad59d742e90e974e"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_storage/storage.go b/pkg/detectors/azure_storage/storage.go
deleted file mode 100644
index c48496a81610..000000000000
--- a/pkg/detectors/azure_storage/storage.go
+++ /dev/null
@@ -1,197 +0,0 @@
-package azure_storage
-
-import (
- "context"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/base64"
- "encoding/xml"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
-
- namePat = regexp.MustCompile(`(?i:Account[_.-]?Name|Storage[_.-]?(?:Account|Name))(?:.|\s){0,20}?\b([a-z0-9]{3,24})\b|([a-z0-9]{3,24})(?i:\.blob\.core\.windows\.net)`) // Names can only be lowercase alphanumeric.
- keyPat = regexp.MustCompile(`(?i:(?:Access|Account|Storage)[_.-]?Key)(?:.|\s){0,25}?([a-zA-Z0-9+\/-]{86,88}={0,2})`)
-
- // https://learn.microsoft.com/en-us/azure/storage/common/storage-use-emulator
- testNames = map[string]struct{}{
- "devstoreaccount1": {},
- "storagesample": {},
- }
- testKeys = map[string]struct{}{
- "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==": {},
- }
-)
-
-func (s Scanner) Keywords() []string {
- return []string{
- "DefaultEndpointsProtocol=http", "EndpointSuffix", "core.windows.net",
- "AccountName", "Account_Name", "Account.Name", "Account-Name",
- "StorageAccount", "Storage_Account", "Storage.Account", "Storage-Account",
- "AccountKey", "Account_Key", "Account.Key", "Account-Key",
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureStorage
-}
-
-func (s Scanner) Description() string {
- return "Azure Storage is a Microsoft-managed cloud service that provides storage that is highly available, secure, durable, scalable, and redundant. Azure Storage Account keys can be used to access and manage data within storage accounts."
-}
-
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- // Deduplicate results.
- names := make(map[string]struct{})
- for _, matches := range namePat.FindAllStringSubmatch(dataStr, -1) {
- var name string
- if matches[1] != "" {
- name = matches[1]
- } else {
- name = matches[2]
- }
- if _, ok := testNames[name]; ok {
- continue
- }
- names[name] = struct{}{}
- }
- if len(names) == 0 {
- return results, nil
- }
-
- keys := make(map[string]struct{})
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- key := matches[1]
- if _, ok := testKeys[key]; ok {
- continue
- }
- keys[key] = struct{}{}
- }
- if len(keys) == 0 {
- return results, nil
- }
-
- // Check results.
- for name := range names {
- var s1 detectors.Result
- for key := range keys {
- s1 = detectors.Result{
- DetectorType: s.Type(),
- Raw: []byte(key),
- RawV2: []byte(fmt.Sprintf(`{"accountName":"%s","accountKey":"%s"}`, name, key)),
- ExtraData: map[string]string{
- "Account_name": name,
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := s.verifyMatch(ctx, client, name, key, s1.ExtraData)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, key)
- }
-
- results = append(results, s1)
- if s1.Verified {
- break
- }
- }
- }
-
- return results, nil
-}
-
-type storageResponse struct {
- Containers struct {
- Container []container `xml:"Container"`
- } `xml:"Containers"`
-}
-
-type container struct {
- Name string `xml:"Name"`
-}
-
-func (s Scanner) verifyMatch(ctx context.Context, client *http.Client, name string, key string, extraData map[string]string) (bool, error) {
- // https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
- now := time.Now().UTC().Format(http.TimeFormat)
- stringToSign := "GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:" + now + "\nx-ms-version:2019-12-12\n/" + name + "/\ncomp:list"
- accountKeyBytes, _ := base64.StdEncoding.DecodeString(key)
- h := hmac.New(sha256.New, accountKeyBytes)
- h.Write([]byte(stringToSign))
- signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
-
- url := "https://" + name + ".blob.core.windows.net/?comp=list"
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Set("x-ms-date", now)
- req.Header.Set("x-ms-version", "2019-12-12")
- req.Header.Set("Authorization", "SharedKey "+name+":"+signature)
-
- res, err := client.Do(req)
- if err != nil {
- // If the host is not found, we can assume that the accountName is not valid
- if strings.Contains(err.Error(), "no such host") {
- return false, nil
- }
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- // parse response
- response := storageResponse{}
- if err := xml.NewDecoder(res.Body).Decode(&response); err != nil {
- return false, err
- }
-
- // update the extra data with container names only
- if len(response.Containers.Container) > 0 {
- var b strings.Builder
- for i, c := range response.Containers.Container {
- if i > 0 {
- b.WriteString(", ")
- }
- b.WriteString(c.Name)
- }
- extraData["container_names"] = b.String()
- }
-
- return true, nil
- case http.StatusForbidden:
- // 403 if account id or key is invalid, or if the account is disabled
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
diff --git a/pkg/detectors/azure_storage/storage_integration_test.go b/pkg/detectors/azure_storage/storage_integration_test.go
deleted file mode 100644
index 85c742b84da0..000000000000
--- a/pkg/detectors/azure_storage/storage_integration_test.go
+++ /dev/null
@@ -1,205 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azure_storage
-
-import (
- "context"
- "fmt"
- "strings"
- "testing"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzurestorage_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AZURE_STORAGE")
- inactiveSecret := testSecrets.MustGetField("AZURE_STORAGE_INACTIVE")
-
- accountNamePat := regexp.MustCompile(`AccountName=(?P[^;]+);AccountKey`)
- accountName := accountNamePat.FindStringSubmatch(secret)[1]
- validKeyInvalidAccountName := strings.Replace(secret, accountName, "invalid", 1)
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurestorage secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureStorage,
- Verified: true,
- ExtraData: map[string]string{
- "account_name": "teststoragebytruffle",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurestorage secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureStorage,
- Verified: false,
- ExtraData: map[string]string{
- "account_name": "teststoragebytruffle",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurestorage secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureStorage,
- Verified: false,
- ExtraData: map[string]string{
- "account_name": "teststoragebytruffle",
- },
- }
- r.SetVerificationError(fmt.Errorf("context deadline exceeded"), secret)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurestorage secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureStorage,
- Verified: false,
- ExtraData: map[string]string{
- "account_name": "teststoragebytruffle",
- },
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 404"), secret)
- return []detectors.Result{r}
- }(),
- wantErr: false,
- },
- {
- name: "found secret with invalid account name",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurestorage secret %s within", validKeyInvalidAccountName)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureStorage,
- Verified: false,
- ExtraData: map[string]string{
- "account_name": "invalid",
- },
- },
- },
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Azuretorage.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", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Azurestorage.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azure_storage/storage_test.go b/pkg/detectors/azure_storage/storage_test.go
deleted file mode 100644
index 1da26b3e59f5..000000000000
--- a/pkg/detectors/azure_storage/storage_test.go
+++ /dev/null
@@ -1,321 +0,0 @@
-package azure_storage
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureStorage_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- // True Positive
- // CONNECTION STRINGS
- {
- name: `connection_string_1`,
- input: `DefaultEndpointsProtocol=https;AccountName=storagetest123;AccountKey=YutGV0Vlauqsobd5tPWz2AKwHhBXMEWsAH+rSbz0UZUfaMVj1CFrcNQK47ygmrC4vHmc7eOp1LdM+AStk5mMyA==;EndpointSuffix=core.windows.net`,
- want: []string{`{"accountName":"storagetest123","accountKey":"YutGV0Vlauqsobd5tPWz2AKwHhBXMEWsAH+rSbz0UZUfaMVj1CFrcNQK47ygmrC4vHmc7eOp1LdM+AStk5mMyA=="}`},
- },
- {
- name: `connection_string_2`,
- input: `EndpointSuffix=core.windows.net;AccountKey=ldlKgoKPJhRjPJTkaC5c/QNqtu4sVQRc/teGJ0MZHbDYEHdvBV5z8JEfJK+evE87D7U8TzMZ0G2C+ASt2B4ifg==;AccountName=storagetest123;DefaultEndpointsProtocol=http`,
- want: []string{`{"accountName":"storagetest123","accountKey":"ldlKgoKPJhRjPJTkaC5c/QNqtu4sVQRc/teGJ0MZHbDYEHdvBV5z8JEfJK+evE87D7U8TzMZ0G2C+ASt2B4ifg=="}`},
- },
- {
- name: `connection_string_3`,
- input: ` public const string SharedStorageKey = "DefaultEndpointsProtocol=https;AccountName=huntappstorage;AccountKey=rrttFty/b52ET/e8VqpMSN+ZqAUP7hcXVkdekrPX58gsMZyOCrE+igN07t3lyi7tAV0+OrJFBaDtMe06YJ2tFw==;EndpointSuffix=core.windows.net";`,
- want: []string{`{"accountName":"huntappstorage","accountKey":"rrttFty/b52ET/e8VqpMSN+ZqAUP7hcXVkdekrPX58gsMZyOCrE+igN07t3lyi7tAV0+OrJFBaDtMe06YJ2tFw=="}`},
- },
- {
- name: `connection_string_multiline`,
- input: `
-export const DevelopmentConnectionString = 'DefaultEndpointsProtocol=http;AccountName=macdemostorage;
- AccountKey=Jby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;
- QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;';`,
- want: []string{`{"accountName":"macdemostorage","accountKey":"Jby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="}`},
- },
-
- // LANGUAGES
- // TODO:
- // - https://github.com/Satyamk21/az204/blob/75f340c5bbfb34c1477a6885e216d5ae0972a380/Lab%203.txt#L22
-
- // https://github.com/facebookincubator/velox/blob/98e958c0df498efd7cf44a2078cc71caeb7aed23/velox/connectors/hive/storage_adapters/abfs/tests/AzuriteServer.h#L32-L36
- {
- name: `cpp`,
- input: `static const std::string AzuriteAccountName{"storagetest123"};
-static const std::string AzuriteContainerName{"test"};
-// the default key of Azurite Server used for connection
-static const std::string AzuriteAccountKey{
- "qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA=="};`,
- want: []string{`{"accountName":"storagetest123","accountKey":"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA=="}`},
- },
- // https://github.com/MicrosoftDX/Dash/blob/03c4bb55f9e84fd03ee943559c128c4d5c2a31c2/DashServer.Tests/RequestAuthTests.cs#L29
- {
- name: `dotnet1`,
- input: ` _ctx = InitializeConfigAndCreateTestBlobs(ctx, "datax1", new Dictionary
- {
- { "AccountName", "dashstorage1" },
- { "AccountKey", "8jqRVtXUWiEthgIhR+dFwrB8gh3lFuquvJQ1v4eabObIj7okI1cZIuzY8zZHmEdpcC0f+XlUkbFwAhjTfyrLIg==" },
- { "SecondaryAccountKey", "Klari9ZbVdFQ35aULCfqqehCsd136amhusMHWynTpz2Pg+GyQMJw3GH177hvEQbaZ2oeRYk3jw0mIaV3ehNIRg==" },
- },`,
- want: []string{
- `{"accountName":"dashstorage1","accountKey":"8jqRVtXUWiEthgIhR+dFwrB8gh3lFuquvJQ1v4eabObIj7okI1cZIuzY8zZHmEdpcC0f+XlUkbFwAhjTfyrLIg=="}`,
- `{"accountName":"dashstorage1","accountKey":"Klari9ZbVdFQ35aULCfqqehCsd136amhusMHWynTpz2Pg+GyQMJw3GH177hvEQbaZ2oeRYk3jw0mIaV3ehNIRg=="}`,
- },
- },
- // https://github.com/Satyamk21/az204/blob/75f340c5bbfb34c1477a6885e216d5ae0972a380/Lab%203.txt#L11
- {
- name: `dotnet2`,
- input: `public class Program
-{
- private const string blobServiceEndpoint = "https://k21storagemedia.blob.core.windows.net/";
-
- private const string storageAccountName = "k21storagemedia";
-
- private const string storageAccountKey = "DFdukxfl0SwO4NB91bi/FTPh9BMEKr6Z5wWf+tGDfXMakXvGVp/NDzAUjWc/9171OqoDvXSj1o8N+AStUk1GXg==";
-
-
- //The following code to create a new asynchronous Main method
- public static async Task Main(string[] args)`,
- want: []string{`{"accountName":"k21storagemedia","accountKey":"DFdukxfl0SwO4NB91bi/FTPh9BMEKr6Z5wWf+tGDfXMakXvGVp/NDzAUjWc/9171OqoDvXSj1o8N+AStUk1GXg=="}`},
- },
- // https://github.com/apache/camel/blob/main/test-infra/camel-test-infra-azure-common/src/test/java/org/apache/camel/test/infra/azure/common/services/AzuriteContainer.java#L25-L27
- {
- name: `java`,
- input: `public class AzuriteContainer extends GenericContainer {
- public static final String DEFAULT_ACCOUNT_NAME = "storagetest123";
- public static final String DEFAULT_ACCOUNT_KEY
- = "qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==";
-
- public static final String IMAGE_NAME = "mcr.microsoft.com/azure-storage/azurite:3.27.0";`,
- want: []string{`{"accountName":"storagetest123","accountKey":"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA=="}`},
- },
- // https://github.com/Azure/azure-storage-node/blob/6873387fc65bad6d577babe278be2ee2e6071493/test/common/connectionstringparsertests.js
- {
- name: `javascript`,
- input: ` var parsedConnectionString = ServiceSettings.parseAndValidateKeys(defaultConnectionString + endpointsConnectionString, validKeys);
- assert.equal(parsedConnectionString['DefaultEndpointsProtocol'], 'https');
- assert.equal(parsedConnectionString['AccountName'], 'storagetest123');
- assert.equal(parsedConnectionString['AccountKey'], 'KWPLd0rpW2T0U7K2pVpF8rYr1BgYtR7wYQk33AYiXeUoquiaY6o0TWqduxmPHlqeCNZ3LU0DHptbeIHy5l/Yhg==');
- assert.equal(parsedConnectionString['BlobEndpoint'], 'myBlobEndpoint');
- assert.equal(parsedConnectionString['QueueEndpoint'], 'myQueueEndpoint');
- assert.equal(parsedConnectionString['TableEndpoint'], 'myTableEndpoint');`,
- want: []string{`{"accountName":"storagetest123","accountKey":"KWPLd0rpW2T0U7K2pVpF8rYr1BgYtR7wYQk33AYiXeUoquiaY6o0TWqduxmPHlqeCNZ3LU0DHptbeIHy5l/Yhg=="}`},
- },
- // https://github.com/nextcloud/server/blob/81a9e19ace190ea0a64d52d95d341e25c7ad618b/tests/preseed-config.php#L89
- {
- name: `php`,
- input: ` 'arguments' => [
- 'container' => 'test',
- 'account_name' => 'storagetest123',
- 'account_key' => 'qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==',
- 'endpoint' => 'http://' . (getenv('DRONE') === 'true' ? 'azurite' : 'localhost') . ':10000/devstoreaccount1',
- 'autocreate' => true
- ]`,
- want: []string{`{"accountName":"storagetest123","accountKey":"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA=="}`},
- },
- // https://github.com/Azure/azure-sdk-for-js/blob/2719dcfbe835a2da3003876dcb5d77efba95f912/sdk/cosmosdb/cosmos/test/public/common/_fakeTestSecrets.ts
- {
- name: `typescript`,
- input: `export const name =
- process.env.ACCOUNT_NAME || "storagename123";
-export const key =
- process.env.ACCOUNT_KEY ||
- "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";`,
- want: []string{`{"accountName":"storagename123","accountKey":"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="}`},
- },
-
- // FORMATS
- // TODO: Doesn't work.
- // https://github.com/Azure/azure-quickstart-templates/blob/03e792429fbc65c9353335611933746364590b22/quickstarts/microsoft.datafactory/data-factory-hive-transformation/azuredeploy.parameters.json#L9C38-L9C38
- // {
- // name: `json`,
- // input: ` "storageAccountName": {
- // "value": "changemeazurestorage"
- //},
- //"storageAccountKey": {
- // "value": "YA1gKAMY34PeVgEWPF8FdbQO+U0nFkd3SaFE4d32K16AYL/DowrTYun8anOdAiCnMkCiRYm+PxUh5mw7a7lVcA=="
- //},`,
- // want: []string{`{"accountName":"storagetest123","accountKey":"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA=="}`},
- // },
- // https://github.com/ClickHouse/ClickHouse/blob/eba52b318d67d85330c9c1781499b7ff27fb7c0e/tests/integration/test_storage_azure_blob_storage/configs/named_collections.xml
- {
- name: `xml`,
- input: `
- storagetest123
- qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==
- `,
- want: []string{`{"accountName":"storagetest123","accountKey":"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA=="}`},
- },
- // https://github.com/hubblestack/hubble/blob/f9b7bf38752bd16b27d050a3b8787652a1c6319b/hubblestack/fileserver/azurefs.py
- {
- name: `yaml1`,
- input: ` azurefs:
- - account_name: mystorage
- account_key: 'fNH9cRp0+qVIVYZ+5rnZAhHc9ycOUcJnHtzpfOr0W0sxrtL2KVLuMe1xDfLwmfed+JJInZaEdWVCPHD4d/oqeA=='
- container_name: my_container
- proxy: 10.10.10.10:8080`,
- want: []string{`{"accountName":"mystorage","accountKey":"fNH9cRp0+qVIVYZ+5rnZAhHc9ycOUcJnHtzpfOr0W0sxrtL2KVLuMe1xDfLwmfed+JJInZaEdWVCPHD4d/oqeA=="}`},
- },
- {
- name: `yaml2`,
- input: ` - name: filesharevolume
- azureFile:
- sharename: containershare
- storageAccountName: newstore100033323
- storageAccountKey: Ar4/2iY8L0rEMeQaijINnfaMJr7vqjfbPgmJayw6Pu5l9ZI+GrFDm1uIWOqXk5RQLrTiXfBwWY6hAbPEIQqy1g==`,
- want: []string{`{"accountName":"newstore100033323","accountKey":"Ar4/2iY8L0rEMeQaijINnfaMJr7vqjfbPgmJayw6Pu5l9ZI+GrFDm1uIWOqXk5RQLrTiXfBwWY6hAbPEIQqy1g=="}`},
- },
- // This was manually base64-decoded since that doesn't work in unit tests.
- // https://github.com/fabric8io/configmapcontroller/blob/master/vendor/k8s.io/kubernetes/examples/azure_file/secret/azure-secret.yaml
- {
- name: `yaml_3`,
- input: `apiVersion: v1
- kind: Secret
- metadata:
- name: azure-secret
- type: Opaque
- data:
- azurestorageaccountname: k8stest
- azurestorageaccountkey: xIF1zJbnnojFLMSkBp50mx02rHsMK2sjU7mFt4L13hoB7drAaJ8jD6+A443jJogV7y2FUOhQCWPmM6YaNHy7qg==
-`,
- want: []string{`{"accountName":"k8stest","accountKey":"xIF1zJbnnojFLMSkBp50mx02rHsMK2sjU7mFt4L13hoB7drAaJ8jD6+A443jJogV7y2FUOhQCWPmM6YaNHy7qg=="}`},
- },
-
- // MISC
- // https://github.com/Azure-Samples/nested-virtualization-image-builder/blob/cf0373a421343b00ce3d261be99ddced80deb55b/README.md?plain=1#L54
- {
- name: `blob_url`,
- input: `"name": "storagetest123.blob.core.windows.net", "accountKey":"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w=="`,
- want: []string{`{"accountName":"storagetest123","accountKey":"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w=="}`},
- },
- {
- name: `random_cli_1`,
- input: `go run .\main.go -debug -dest="https://kenfau.blob.core.windows.net/ss3/" -AzureDefaultAccountName="kenfoo" -AzureDefaultAccountKey="hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w=="`,
- want: []string{
- `{"accountName":"kenfau","accountKey":"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w=="}`,
- `{"accountName":"kenfoo","accountKey":"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w=="}`,
- },
- },
- // - https://github.com/nwoolls/AzureStorageCleanup/blob/980e5cb163c78e9446e70d2513ba5a7ed9051a7a/README.md?plain=1#L24
- {
- name: `random_cli_2`,
- input: `AzureStorageCleanup.exe
- -storagename storageaccount
- -storagekey hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w==
- -container sqlbackup
- -mindaysold 60
- -searchpattern .*
- -recursive
- -whatif`,
- want: []string{`{"accountName":"storageaccount","accountKey":"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w=="}`},
- },
- // https://github.com/dahlej/rpi-spark-titantic/blob/d00b8f5b4696aeb2113e9452c24bb31b7f9a0242/tmp.txt#L9
- {
- name: `random_cli_3`,
- input: `$ bin/spark-submit --master \
- k8s://test-cluster.eastus2.cloudapp.azure.com:443 \
- --deploy-mode cluster \
- --name copyLocations \
- --class io.timpark.CopyData \
- --conf spark.copydata.containerpath=wasb://containers@storagetest123.blob.core.windows.net \
- --conf spark.copydata.storageaccount=storagetest123 \
- --conf spark.copydata.storageaccountkey=hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w== \`,
- want: []string{`{"accountName":"storagetest123","accountKey":"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w=="}`},
- },
- {
- name: `custom_config_1`,
- input: `driver := ArtifactDriver{
- AccountName: "storagetest123",
- AccountKey: "qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==",
- Container: "test",
- }`,
- want: []string{`{"accountName":"storagetest123","accountKey":"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA=="}`},
- },
- // https://github.com/MicrosoftDX/Dash/blob/master/LoadTestDotNet/GetBlobCoded.cs
- {
- name: `storage_account_1`,
- input: ` this.Context.Add("StorageEndPoint", "http://dashstorage3.blob.core.windows.net");
- this.Context.Add("StorageAccount", "dashstorage3");
- this.Context.Add("AccountKey", "TP+G/9FTZRP1he1EpKilMercxSbMyqtaI9xTbc/3HqT2/FkxyIk1wVlBdemDFuYKStmlkFqHc7049l8McTd8NQ==");
- this.Context.Add("SendChunked", false);`,
- want: []string{`{"accountName":"dashstorage3","accountKey":"TP+G/9FTZRP1he1EpKilMercxSbMyqtaI9xTbc/3HqT2/FkxyIk1wVlBdemDFuYKStmlkFqHc7049l8McTd8NQ=="}`},
- },
- // https://github.com/kubecost/poc-common-configurations/blob/d626a48824a104e3089fc66ef57029f1e2212f6a/keys.txt#L18
- {
- name: `storage_account_2`,
- input: `AZ_cloud_integration_subscriptionId:0bd50fdf-c923-4e1e-850c-196ddSAMPLE
-AZ_cloud_integration_azureStorageAccount:kubecostexport
-AZ_cloud_integration_azureStorageAccessKey:TP+G/9FTZRP1he1EpKilMercxSbMyqtaI9xTbc/3HqT2/FkxyIk1wVlBdemDFuYKStmlkFqHc7049l8McTd8NQ==
-AZ_cloud_integration_azureStorageContainer:costexports`,
- want: []string{`{"accountName":"kubecostexport","accountKey":"TP+G/9FTZRP1he1EpKilMercxSbMyqtaI9xTbc/3HqT2/FkxyIk1wVlBdemDFuYKStmlkFqHc7049l8McTd8NQ=="}`},
- },
-
- // False positives
- {
- name: `test_key`,
- input: ` azureblockblob: TEST_BACKEND=azureblockblob://DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;`,
- },
- {
- name: `test_key_multiline`,
- input: `
-export const DevelopmentConnectionString = 'DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;
- AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;
- QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;';`,
- },
- {
- name: `invalid_key_1`,
- input: ` docs::examples = "DefaultEndpointsProtocol=https;AccountName=mylogstorage;AccountKey=storageaccountkeybase64encoded;EndpointSuffix=core.windows.net"`,
- },
- {
- name: `invalid_key_2`,
- input: `PS C:\> Add-AzIotHubRoutingEndpoint -ResourceGroupName "myresourcegroup" -Name "myiothub" -EndpointName S1 -EndpointType AzureStorageContainer -EndpointResourceGroup resourcegroup1 -EndpointSubscriptionId 91d12343-a3de-345d-b2ea-135792468abc -ConnectionString 'DefaultEndpointsProtocol=https;AccountName=mystorage1;AccountKey=*****;EndpointSuffix=core.windows.net' -ContainerName container -Encoding json`,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azureapimanagement/repositorykey/repositorykey.go b/pkg/detectors/azureapimanagement/repositorykey/repositorykey.go
deleted file mode 100644
index 9a2241c80411..000000000000
--- a/pkg/detectors/azureapimanagement/repositorykey/repositorykey.go
+++ /dev/null
@@ -1,161 +0,0 @@
-package repositorykey
-
-import (
- "context"
- "errors"
- "fmt"
- "net/url"
- "os/exec"
- "strconv"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- urlPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure", "url"}) + `([a-z0-9][a-z0-9-]{0,48}[a-z0-9]\.scm\.azure-api\.net)`)
- passwordPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure", "password"}) + `\b(git&[0-9]{12}&[a-zA-Z0-9\/+]{85}[a-zA-Z0-9]==)`)
-
- invalidHosts = simple.NewCache[struct{}]()
- noSuchHostErr = errors.New("Could not resolve host")
-)
-
-const (
- azureGitUsername = "apim"
-)
-
-// 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{"azure", ".scm.azure-api.net"}
-}
-
-// FromData will find and optionally verify AzureDevopsPersonalAccessToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- logger := logContext.AddLogger(ctx).Logger().WithName("azurecr")
- dataStr := string(data)
-
- // Deduplicate matches.
- uniqueUrlsMatches := make(map[string]struct{})
- uniquePasswordMatches := make(map[string]struct{})
-
- for _, matches := range urlPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueUrlsMatches[strings.TrimSpace(matches[1])] = struct{}{}
- }
-
- for _, matches := range passwordPat.FindAllStringSubmatch(dataStr, -1) {
- uniquePasswordMatches[strings.TrimSpace(matches[1])] = struct{}{}
- }
-
-EndpointLoop:
- for urlMatch := range uniqueUrlsMatches {
- for passwordMatch := range uniquePasswordMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureApiManagementRepositoryKey,
- Raw: []byte(passwordMatch),
- RawV2: []byte(urlMatch + passwordMatch),
- }
-
- if verify {
- if invalidHosts.Exists(urlMatch) {
- logger.V(3).Info("Skipping invalid registry", "url", urlMatch)
- continue EndpointLoop
- }
-
- isVerified, err := verifyUrlPassword(ctx, urlMatch, azureGitUsername, passwordMatch)
- s1.Verified = isVerified
- if err != nil {
- if errors.Is(err, noSuchHostErr) {
- invalidHosts.Set(urlMatch, struct{}{})
- continue EndpointLoop
- }
- s1.SetVerificationError(err, urlMatch)
- }
- }
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureApiManagementRepositoryKey
-}
-
-func (s Scanner) Description() string {
- return "Azure API Management Repository Keys provide access to the API Management (APIM) configuration repository, allowing users to directly interact with and modify API definitions, policies, and settings. These keys enable programmatic access to APIM's Git-based repository, where configurations can be cloned, edited, and pushed back to apply changes. They are primarily used for managing API configurations as code, automating deployments, and synchronizing APIM settings across environments."
-}
-
-func gitCmdCheck() error {
- if errors.Is(exec.Command("git").Run(), exec.ErrNotFound) {
- return fmt.Errorf("'git' command not found in $PATH. Make sure git is installed and included in $PATH")
- }
-
- // Check the version is greater than or equal to 2.20.0
- out, err := exec.Command("git", "--version").Output()
- if err != nil {
- return fmt.Errorf("failed to check git version: %w", err)
- }
-
- // Extract the version string using a regex to find the version numbers
- var regex = regexp.MustCompile(`\d+\.\d+\.\d+`)
-
- versionStr := regex.FindString(string(out))
- versionParts := strings.Split(versionStr, ".")
-
- // Parse version numbers
- major, _ := strconv.Atoi(versionParts[0])
- minor, _ := strconv.Atoi(versionParts[1])
-
- // Compare with version 2.20.0<=x<3.0.0
- if major == 2 && minor >= 20 {
- return nil
- }
- return fmt.Errorf("git version is %s, but must be greater than or equal to 2.20.0, and less than 3.0.0", versionStr)
-}
-
-func verifyUrlPassword(_ context.Context, repoUrl, user, password string) (bool, error) {
- if err := gitCmdCheck(); err != nil {
- return false, err
- }
-
- parsedURL, err := url.Parse(repoUrl)
- if err != nil {
- return false, err
- }
-
- if parsedURL.User == nil {
- parsedURL.User = url.UserPassword(user, password)
- }
- parsedURL.Scheme = "https" // Force HTTPS
-
- fakeRef := "TRUFFLEHOG_CHECK_GIT_REMOTE_URL_REACHABILITY"
- gitArgs := []string{"ls-remote", parsedURL.String(), "--quiet", fakeRef}
- cmd := exec.Command("git", gitArgs...)
- output, err := cmd.CombinedOutput()
- if err != nil {
- outputString := string(output)
- if strings.Contains(outputString, "Authentication failed") {
- return false, nil
- } else if strings.Contains(outputString, "Could not resolve host") {
- return false, noSuchHostErr
- }
- return false, err
- }
-
- return true, nil
-}
diff --git a/pkg/detectors/azureapimanagement/repositorykey/repositorykey_integration_test.go b/pkg/detectors/azureapimanagement/repositorykey/repositorykey_integration_test.go
deleted file mode 100644
index 07feb120b5bc..000000000000
--- a/pkg/detectors/azureapimanagement/repositorykey/repositorykey_integration_test.go
+++ /dev/null
@@ -1,135 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package repositorykey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAxonaut_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- url := testSecrets.MustGetField("AZURE_API_MGMT_REPOSITORY_KEY_URL")
- inactiveUrl := testSecrets.MustGetField("AZURE_API_MGMT_REPOSITORY_KEY_URL_INACTIVE")
- password := testSecrets.MustGetField("AZURE_API_MGMT_REPOSITORY_KEY_PASSWORD")
- inactivePassword := testSecrets.MustGetField("AZURE_API_MGMT_REPOSITORY_KEY_PASSWORD_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure repository url %s password %s", url, password)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureApiManagementRepositoryKey,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure repository url %s password %s but unverified", url, inactivePassword)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureApiManagementRepositoryKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- {
- name: "found, host not resolved",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure repository url %s password %s but unverified", inactiveUrl, inactivePassword)),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AzureApiManagementRepositoryKey.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])
- }
- }
-
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "ExtraData", "AnalysisInfo")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AzureApiManagementRepositoryKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azureapimanagement/repositorykey/repositorykey_test.go b/pkg/detectors/azureapimanagement/repositorykey/repositorykey_test.go
deleted file mode 100644
index 9f882fbbda4c..000000000000
--- a/pkg/detectors/azureapimanagement/repositorykey/repositorykey_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package repositorykey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureAPIManagementRepositoryKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: `valid pattern`,
- input: `
- AZURE_URL=https://test.scm.azure-api.net
- PASSWORD=git&202503251200&R2xlVEmqi+OW130dxWIDhfw1K6XKw/gxc5P9te3cwWBtnK2XkZq5k+VUAdnuX1Y0T/I5CRK9fJyBJr31SmFEYw==
- `,
- want: []string{"test.scm.azure-api.netgit&202503251200&R2xlVEmqi+OW130dxWIDhfw1K6XKw/gxc5P9te3cwWBtnK2XkZq5k+VUAdnuX1Y0T/I5CRK9fJyBJr31SmFEYw=="},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {url 726o3.scm.azure-api.net}
- {password AQAAABAAA git&303102631708&ZidF02ZVakrtuWcW00cgvhZ6YUiZbIsZ84bE3u01jOXdKv7VXr0t6DE9OtdJnUTaBAz843vSDvVpCjRFEYSJq3==}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"726o3.scm.azure-api.netgit&303102631708&ZidF02ZVakrtuWcW00cgvhZ6YUiZbIsZ84bE3u01jOXdKv7VXr0t6DE9OtdJnUTaBAz843vSDvVpCjRFEYSJq3=="},
- },
- {
- name: `invalid host pattern`,
- input: `
- AZURE_URL=https://test.scm.azure.net
- PASSWORD=git&202503251200&R2xlVEmqi+OW130dxWIDhfw1K6XKw/gxc5P9te3cwWBtnK2XkZq5k+VUAdnuX1Y0T/I5CRK9fJyBJr31SmFEYw==
- `,
- want: []string{},
- },
- {
- name: `invalid password pattern without ==`,
- input: `
- AZURE_URL=https://test.scm.azure-api.net
- PASSWORD=git&202503251200&R2xlVEmqi+OW130dxWIDhfw1K6XKw/gxc5P9te3cwWBtnK2XkZq5k+VUAdnuX1Y0T/I5CRK9fJyBJr31SmFEYw=
- `,
- want: []string{},
- },
- {
- name: `invalid password pattern with wrong expiry date`,
- input: `
- AZURE_URL=https://test.scm.azure-api.net
- PASSWORD=git&20250325&R2xlVEmqi+OW130dxWIDhfw1K6XKw/gxc5P9te3cwWBtnK2XkZq5k+VUAdnuX1Y0T/I5CRK9fJyBJr31SmFEYw==
- `,
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey.go b/pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey.go
deleted file mode 100644
index 6fea8fc49e3d..000000000000
--- a/pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package azureapimanagementsubscriptionkey
-
-import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- urlPat = regexp.MustCompile(`https://([a-z0-9][a-z0-9-]{0,48}[a-z0-9])\.azure-api\.net`) // https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.APIM.Name/
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure", ".azure-api.net", "subscription", "key"}) + `([a-zA-Z-0-9]{32})`) // pattern for both Primary and secondary key
-
- invalidHosts = simple.NewCache[struct{}]()
- noSuchHostErr = errors.New("no such host")
-)
-
-// 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{".azure-api.net"}
-}
-
-// FromData will find and optionally verify Azure Subscription keys in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- logger := logContext.AddLogger(ctx).Logger().WithName("azureapimanagementsubscriptionkey")
- dataStr := string(data)
-
- urlMatchesUnique := make(map[string]struct{})
- for _, urlMatch := range urlPat.FindAllStringSubmatch(dataStr, -1) {
- urlMatchesUnique[urlMatch[0]] = struct{}{}
- }
- keyMatchesUnique := make(map[string]struct{})
- for _, keyMatch := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatchesUnique[strings.TrimSpace(keyMatch[1])] = struct{}{}
- }
-
-EndpointLoop:
- for baseUrl := range urlMatchesUnique {
- for key := range keyMatchesUnique {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureAPIManagementSubscriptionKey,
- Raw: []byte(baseUrl),
- RawV2: []byte(baseUrl + ":" + key),
- }
-
- if verify {
- if invalidHosts.Exists(baseUrl) {
- logger.V(3).Info("Skipping invalid registry", "baseUrl", baseUrl)
- continue EndpointLoop
- }
-
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := s.verifyMatch(ctx, client, baseUrl, key)
- s1.Verified = isVerified
- if verificationErr != nil {
- if errors.Is(verificationErr, noSuchHostErr) {
- invalidHosts.Set(baseUrl, struct{}{})
- continue EndpointLoop
- }
- s1.SetVerificationError(verificationErr, baseUrl)
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureAPIManagementSubscriptionKey
-}
-
-func (s Scanner) Description() string {
- return "Azure API Management provides a direct management REST API for performing operations on selected entities, such as users, groups, products, and subscriptions."
-}
-
-func (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {
- return false, ""
-}
-
-func (s Scanner) verifyMatch(ctx context.Context, client *http.Client, baseUrl, key string) (bool, error) {
- url := baseUrl + "/echo/resource" // default testing endpoint for api management services
- req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
- if err != nil {
- return false, err
- }
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Ocp-Apim-Subscription-Key", key)
- resp, err := client.Do(req)
- if err != nil {
- return false, nil
- }
- defer resp.Body.Close()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey_integration_test.go b/pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey_integration_test.go
deleted file mode 100644
index db2b19807f95..000000000000
--- a/pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey_integration_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azureapimanagementsubscriptionkey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzureAPIManagementSubscriptionKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- url := testSecrets.MustGetField("AZUREAPIMANAGEMENTSUBSCRIPTIONKEY_URL")
- secret := testSecrets.MustGetField("AZUREDIRECTMANAGEMENTAPISUBSCRIPTIONKEY")
- inactiveSecret := testSecrets.MustGetField("AZUREDIRECTMANAGEMENTAPISUBSCRIPTIONKEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte(fmt.Sprintf("You can find a azure api management gateway url %s and subscription key %s within", url, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureAPIManagementSubscriptionKey,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte(fmt.Sprintf("You can find a azure api management gateway url %s and subscription key %s within but not valid", url, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureAPIManagementSubscriptionKey,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AzureDirectManagementAPIKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "Redacted", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AzureDirectManagementAPIKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey_test.go b/pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey_test.go
deleted file mode 100644
index e55b0fad2e0f..000000000000
--- a/pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package azureapimanagementsubscriptionkey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureAPIManagementSubscriptionKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- AZURE_API_MANAGEMENT_GATEWAY_URL=https://trufflesecuritytest.azure-api.net
- AZURE_API_MANAGEMENT_SUBSCRIPTION_KEY=2c69j0dc327c4929b74d3a832a04266b
- `,
- want: []string{"https://trufflesecuritytest.azure-api.net:2c69j0dc327c4929b74d3a832a04266b"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {https://dffe5e2teoezcct050ch-2au74tmls8jm1p.azure-api.net}
- {AQAAABAAA uEDFd7-zSeH6dwwzLbGjVrAlfgXoV1Xv}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"https://dffe5e2teoezcct050ch-2au74tmls8jm1p.azure-api.net:uEDFd7-zSeH6dwwzLbGjVrAlfgXoV1Xv"},
- },
- {
- name: "invalid pattern",
- input: `
- AZURE_API_MANAGEMENT_GATEWAY_URL=https://trufflesecuritytest.azure-api.net
- AZURE_API_MANAGEMENT_SUBSCRIPTION_KEY=2c69j2dc3f7c4929b74d3a832a042
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring.go b/pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring.go
deleted file mode 100644
index a218ba694937..000000000000
--- a/pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package azureappconfigconnectionstring
-
-import (
- "context"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/base64"
- "fmt"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- connectionStringPat = regexp.MustCompile(`Endpoint=(https:\/\/[a-zA-Z0-9-]+\.azconfig\.io);Id=([a-zA-Z0-9+\/=]+);Secret=([a-zA-Z0-9+\/=]+)`)
-)
-
-// 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{".azconfig.io"}
-}
-
-// FromData will find and optionally verify Azure Management API keys in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keyMatchesUnique := make(map[string][]string)
- for _, keyMatch := range connectionStringPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatchesUnique[strings.TrimSpace(keyMatch[0])] = keyMatch // keep all the matched groups for verification
- }
-
- for connectionString, connectionInfo := range keyMatchesUnique {
- endpoint := connectionInfo[1] // Endpoint
- id := connectionInfo[2] // Id
- secret := connectionInfo[3] // Secret
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureAppConfigConnectionString,
- Raw: []byte(id),
- RawV2: []byte(connectionString),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := s.verifyMatch(ctx, client, endpoint, id, secret)
- s1.Verified = isVerified
-
- if verificationErr != nil && !strings.Contains(verificationErr.Error(), "no such host") { // ignore no such host errors
- s1.SetVerificationError(verificationErr, connectionString)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureAppConfigConnectionString
-}
-
-func (s Scanner) Description() string {
- return "Azure App Configuration is a managed service that centralizes application settings and feature flags, enabling dynamic updates without redeploying applications. Its connection string, which includes the endpoint URL and an access key, securely connects applications to the configuration store."
-}
-
-// generateHMACSignature creates the HMAC-SHA256 signature
-func generateHMACSignature(secret, stringToSign string) (string, error) {
- decodedSecret, err := base64.StdEncoding.DecodeString(secret)
- if err != nil {
- return "", fmt.Errorf("failed to decode secret: %w", err)
- }
-
- h := hmac.New(sha256.New, decodedSecret)
- h.Write([]byte(stringToSign))
- signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
- return signature, nil
-}
-
-// verifyMatch sends a request to the Azure App Configuration REST API to verify the provided credentials
-// https://learn.microsoft.com/en-us/azure/azure-app-configuration/rest-api-authentication-hmac
-func (s Scanner) verifyMatch(ctx context.Context, client *http.Client, endpoint, id, secret string) (bool, error) {
- apiVersion := "1.0"
- requestPath := "/kv"
- query := fmt.Sprintf("?api-version=%s", apiVersion)
- url := fmt.Sprintf("%s%s%s", endpoint, requestPath, query)
-
- // Prepare request
- req, err := http.NewRequest(http.MethodGet, url, nil)
- if err != nil {
- return false, fmt.Errorf("failed to create request: %w", err)
- }
-
- // Set required headers
- host := strings.TrimPrefix(strings.TrimPrefix(endpoint, "https://"), "http://")
- date := time.Now().UTC().Format(http.TimeFormat)
- contentHash := base64.StdEncoding.EncodeToString(sha256.New().Sum(nil)) // SHA256 hash of an empty body
-
- req.Header.Set("Host", host)
- req.Header.Set("Date", date)
- req.Header.Set("x-ms-content-sha256", contentHash)
-
- // Create the string to sign
- stringToSign := fmt.Sprintf("%s\n%s%s\n%s;%s;%s",
- http.MethodGet,
- requestPath,
- query,
- date,
- host,
- contentHash,
- )
-
- // Generate the HMAC signature
- signature, err := generateHMACSignature(secret, stringToSign)
- if err != nil {
- return false, fmt.Errorf("failed to generate HMAC signature: %w", err)
- }
-
- // Set the Authorization header
- authorizationHeader := fmt.Sprintf(
- "HMAC-SHA256 Credential=%s&SignedHeaders=date;host;x-ms-content-sha256&Signature=%s",
- id,
- signature,
- )
- req.Header.Set("Authorization", authorizationHeader)
-
- // Send the request
- resp, err := client.Do(req)
- if err != nil {
- return false, fmt.Errorf("request failed: %w", err)
- }
- defer resp.Body.Close()
-
- // Check the response status
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("got unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring_integration_test.go b/pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring_integration_test.go
deleted file mode 100644
index 7967185254a1..000000000000
--- a/pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring_integration_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azureappconfigconnectionstring
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzureAppConfigConnectionString_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AZURE_APP_CONFIGURATION_CONNECTION_STRING")
- inactiveSecret := testSecrets.MustGetField("AZURE_APP_CONFIGURATION_CONNECTION_STRING_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte(fmt.Sprintf("You can find a azureappconfigconnectionstring secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureAppConfigConnectionString,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte(fmt.Sprintf("You can find a azureappconfigconnectionstring secret %s but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureAppConfigConnectionString,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AzureAppConfigConnectionString.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "RawV2", "Raw", "Redacted", "verificationError")
-
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AzureAppConfigConnectionString.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring_test.go b/pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring_test.go
deleted file mode 100644
index 0db2943990b4..000000000000
--- a/pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package azureappconfigconnectionstring
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureAppConfigConnectionString_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `Endpoint=https://trufflesecurity.azconfig.io;Id=u+De;Secret=80DtxZkndXpM2mV2J1JjX2vL1x4gm1hHn8Y3JeFJ4N0PPLSO5D70JQQJ99BBAC1i4FpQkb5wAAACAAZC26dr`,
- want: []string{"Endpoint=https://trufflesecurity.azconfig.io;Id=u+De;Secret=80DtxZkndXpM2mV2J1JjX2vL1x4gm1hHn8Y3JeFJ4N0PPLSO5D70JQQJ99BBAC1i4FpQkb5wAAACAAZC26dr"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {connectionstring}
- {AQAAABAAA Endpoint=https://iTHzRfnepCddRiYoBbPj-drVzUjwTNduwb3EUOTsuSAgg1e83Q7bw.azconfig.io;Id=eO04L+/m9rYn;Secret=G4jQ3GmcsYqlLkkG8uoIVbx08PZIJSdfB/7}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"Endpoint=https://iTHzRfnepCddRiYoBbPj-drVzUjwTNduwb3EUOTsuSAgg1e83Q7bw.azconfig.io;Id=eO04L+/m9rYn;Secret=G4jQ3GmcsYqlLkkG8uoIVbx08PZIJSdfB/7"},
- },
- {
- name: "invalid pattern",
- input: `Endpoint=https://trufflesecurity.azconfig.io;Secret=80DtxZkndXpMTmV2J3JjX2vL1x4gm1hHn8Y3KeFV4N0PPLSO5D70JQQJ79BBAC1i4FpRkb5wAAACAAZC26dr`,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azurecontainerregistry/azurecontainerregistry.go b/pkg/detectors/azurecontainerregistry/azurecontainerregistry.go
deleted file mode 100644
index 74bd6151216b..000000000000
--- a/pkg/detectors/azurecontainerregistry/azurecontainerregistry.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package azurecontainerregistry
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- urlPat = regexp.MustCompile(`([a-z0-9][a-z0-9-]{1,100}[a-z0-9])\.azurecr\.io`)
- passwordPat = regexp.MustCompile(`\b[a-zA-Z0-9+/]{42}\+ACR[a-zA-Z0-9]{6}\b`)
-
- invalidHosts = simple.NewCache[struct{}]()
-)
-
-// 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{".azurecr.io"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureContainerRegistry
-}
-
-func (s Scanner) Description() string {
- return "Azure's container registry is used to store docker containers. An API key can be used to override existing containers, read sensitive data, and do other operations inside the container registry."
-}
-
-// FromData will find and optionally verify Azurecontainerregistry secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- logger := logContext.AddLogger(ctx).Logger().WithName("azurecr")
- dataStr := string(data)
-
- // Deduplicate matches.
- registryMatches := make(map[string]struct{})
- for _, matches := range urlPat.FindAllStringSubmatch(dataStr, -1) {
- u := matches[1]
- // Ignore https://learn.microsoft.com/en-us/azure/container-registry/container-registry-private-link
- if u == "privatelink" || u == "myacr" {
- continue
- }
- registryMatches[u] = struct{}{}
- }
- passwordMatches := make(map[string]struct{})
- for _, matches := range passwordPat.FindAllStringSubmatch(dataStr, -1) {
- p := matches[0]
- if detectors.StringShannonEntropy(p) < 4 {
- continue
- }
- passwordMatches[p] = struct{}{}
- }
-
-EndpointLoop:
- for username := range registryMatches {
- for password := range passwordMatches {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureContainerRegistry,
- Raw: []byte(password),
- RawV2: []byte(`{"username":"` + username + `","password":"` + password + `"}`),
- Redacted: username,
- }
-
- if verify {
- if invalidHosts.Exists(username) {
- logger.V(3).Info("Skipping invalid registry", "username", username)
- continue EndpointLoop
- }
-
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyMatch(ctx, client, username, password)
- if isVerified {
- delete(passwordMatches, password)
- r.Verified = true
- }
- if verificationErr != nil {
- if errors.Is(verificationErr, noSuchHostErr) {
- invalidHosts.Set(username, struct{}{})
- continue EndpointLoop
- }
- r.SetVerificationError(verificationErr, password)
- }
- }
-
- results = append(results, r)
- if r.Verified {
- break
- }
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {
- return false, ""
-}
-
-var noSuchHostErr = errors.New("no such host")
-
-func verifyMatch(ctx context.Context, client *http.Client, username string, password string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://%s.azurecr.io/v2/", username), nil)
- if err != nil {
- return false, err
- }
-
- req.SetBasicAuth(username, password)
- res, err := client.Do(req)
- if err != nil {
- // lookup foo.azurecr.io: no such host
- if strings.Contains(err.Error(), "no such host") {
- return false, noSuchHostErr
- }
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified.
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
diff --git a/pkg/detectors/azurecontainerregistry/azurecontainerregistry_integration_test.go b/pkg/detectors/azurecontainerregistry/azurecontainerregistry_integration_test.go
deleted file mode 100644
index 8ed032f97796..000000000000
--- a/pkg/detectors/azurecontainerregistry/azurecontainerregistry_integration_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azurecontainerregistry
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzureContainerRegistry_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- azureHost := testSecrets.MustGetField("AZURE_CR_HOST")
- password := testSecrets.MustGetField("AZURE_CR_PASSWORD")
- passwordInactive := testSecrets.MustGetField("AZURE_CR_PASSWORD_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurecontainerregistry secret %s and %s within", azureHost, password)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureContainerRegistry,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azurecontainerregistry secret %s and %s within but not valid", azureHost, passwordInactive)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureContainerRegistry,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AzureContainerRegistry.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "RawV2", "Raw", "Redacted", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AzureContainerRegistry.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azurecontainerregistry/azurecontainerregistry_test.go b/pkg/detectors/azurecontainerregistry/azurecontainerregistry_test.go
deleted file mode 100644
index ea25cfd3da26..000000000000
--- a/pkg/detectors/azurecontainerregistry/azurecontainerregistry_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package azurecontainerregistry
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureContainerRegistry_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "pwd",
- input: `source storage.env
- ACR=smpldev.azurecr.io
- ACRUSER=smpldev
- ACRPWD=Cw8xeDNK6Bub3p61aq5ij/TiVvtBicpTj5rverVezj+ACRBPkEcx
- CONTAINER=storage-svc:latest`,
- want: []string{`{"username":"smpldev","password":"Cw8xeDNK6Bub3p61aq5ij/TiVvtBicpTj5rverVezj+ACRBPkEcx"}`},
- },
- {
- name: "password",
- input: ` - name: Deploy to ARC
- uses: azure/docker-login@v1
- with:
- login-server: crmshopacr.azurecr.io
- username: crmshopacr
- password: o9uXSjWlUdRwAeGP2xGSfGy+25vetsONo3Mq13fksa+ACRBXyFsY
- - run: |`,
- want: []string{`{"username":"crmshopacr","password":"o9uXSjWlUdRwAeGP2xGSfGy+25vetsONo3Mq13fksa+ACRBXyFsY"}`},
- },
- {
- name: "docker cli login",
- input: `docker login dvacr00.azurecr.io -u dvacr00 -p Ljc+1lq0U0+c3jHlMHxSxAhCipKt6zU43HfMle/Ymj+ACRAKcPHy
- docker push dvacr00.azurecr.io/foo-alpine:3.18`,
- want: []string{`{"username":"dvacr00","password":"Ljc+1lq0U0+c3jHlMHxSxAhCipKt6zU43HfMle/Ymj+ACRAKcPHy"}`},
- },
- {
- name: "request body",
- input: `"registries":[{"identity":"","passwordSecretRef":"registry-password","server":"cr2bxwtqgom2oo.azurecr.io","username":"cr2bxwtqgom2oo"}],"secrets":[{"name":"registry-password","value":"VP2rvkuld42mr3jNjM+rVbvIzVuZxwncKWyVU5UIad+ACRBivL0B"}]}`,
- want: []string{`{"username":"cr2bxwtqgom2oo","password":"VP2rvkuld42mr3jNjM+rVbvIzVuZxwncKWyVU5UIad+ACRBivL0B"}`},
- },
- {
- name: "README",
- input: `# AZURE-CICD-Deployment-with-Github-Actions
- ## Save pass:
-
- s3cEZKH3yytiVnJ3h+eI3qhhzf9l1vNwEi1+q+WGdd+ACRCZ7JD6
-
-
- ## Run from terminal:
-
- docker build -t testapp.azurecr.io/chicken:latest .
- `,
- want: []string{`{"username":"testapp","password":"s3cEZKH3yytiVnJ3h+eI3qhhzf9l1vNwEi1+q+WGdd+ACRCZ7JD6"}`},
- },
- // TODO:
- //{
- // name: "az cli login",
- // input: `az acr login --name tstcopilotacr --username tstcopilotacr --password 9iZkJiOTKeEsQDfgoobtCYU47EEDs9UvU4L8NErLV+ACRACptmc`,
- // want: []string{},
- //},
- //{
- // name: "",
- // input: ``,
- // want: []string{},
- {
- name: "invalid pattern",
- input: `
- azure:
- url: http://invalid.azurecr.io.azure.com
- secret: BXIMbhBlC3=5hIbqCEKvq7op!V2ZfO0XWbcnasZmPm/AJfQqdcnt/+2Ytxc1hDq1m/
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken.go b/pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken.go
deleted file mode 100644
index d6acb03cc36f..000000000000
--- a/pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package azuredevopspersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure"}) + `\b([0-9a-z]{52})\b`)
- orgPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure"}) + `\b([0-9a-zA-Z][0-9a-zA-Z-]{5,48}[0-9a-zA-Z])\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{"azure"}
-}
-
-// FromData will find and optionally verify AzureDevopsPersonalAccessToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- orgMatches := orgPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, orgMatch := range orgMatches {
- resOrgMatch := strings.TrimSpace(orgMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureDevopsPersonalAccessToken,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resOrgMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- req, err := http.NewRequestWithContext(ctx, "GET", "https://dev.azure.com/"+resOrgMatch+"/_apis/projects", nil)
- if err != nil {
- continue
- }
- req.SetBasicAuth("", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- hasVerifiedRes, _ := common.ResponseContainsSubstring(res.Body, "lastUpdateTime")
- if res.StatusCode >= 200 && res.StatusCode < 300 && hasVerifiedRes {
- s1.Verified = true
- } else if res.StatusCode == 401 {
- // The secret is determinately not verified (nothing to do)
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureDevopsPersonalAccessToken
-}
-
-func (s Scanner) Description() string {
- return "Azure DevOps is a suite of development tools provided by Microsoft. Personal Access Tokens (PATs) are used to authenticate and authorize access to Azure DevOps services and resources."
-}
diff --git a/pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken_integration_test.go b/pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken_integration_test.go
deleted file mode 100644
index 2b7c68f3fd67..000000000000
--- a/pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken_integration_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azuredevopspersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzureDevopsPersonalAccessToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AZURE_DEVOPS_PAT")
- inactiveSecret := testSecrets.MustGetField("AZURE_DEVOPS_PAT_INACTIVE")
- org := testSecrets.MustGetField("AZURE_DEVOPS_PAT_ORG")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s azure organization %s within", secret, org)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureDevopsPersonalAccessToken,
- Verified: true,
- RawV2: []byte(secret + org),
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s azure organization %s within but not valid", inactiveSecret, org)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureDevopsPersonalAccessToken,
- Verified: false,
- RawV2: []byte(inactiveSecret + org),
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s azure organization %s within", secret, org)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureDevopsPersonalAccessToken,
- Verified: false,
- RawV2: []byte(secret + org),
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s azure organization %s within", secret, org)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureDevopsPersonalAccessToken,
- Verified: false,
- RawV2: []byte(secret + org),
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AzureDevopsPersonalAccessToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no rawV2 secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AzureDevopsPersonalAccessToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken_test.go b/pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken_test.go
deleted file mode 100644
index 282461675807..000000000000
--- a/pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package azuredevopspersonalaccesstoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureDevopsPersonalAccessToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- azure:
- azure_key: uie5tff7m5h5lqnqjhaltetqli90a08p6dhv9rn59uo30jgzw8un
- azure_org_id: WOkQXnjSxCyioEJRa8R6J39cN4Xfyy8CWl1BZksHYsevxVBFzG
- `,
- want: []string{"uie5tff7m5h5lqnqjhaltetqli90a08p6dhv9rn59uo30jgzw8unWOkQXnjSxCyioEJRa8R6J39cN4Xfyy8CWl1BZksHYsevxVBFzG"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {azure dlMR9GIBfqCgAPr8qfkBa072OfaP6NbBhCwkPBX0cuHd}
- {azure AQAAABAAA h0wpgbusyba8acyaec1uxxcbxlucgr490c6nvrvd8rylfocwkpg5}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{
- "h0wpgbusyba8acyaec1uxxcbxlucgr490c6nvrvd8rylfocwkpg5dlMR9GIBfqCgAPr8qfkBa072OfaP6NbBhCwkPBX0cuHd",
- "h0wpgbusyba8acyaec1uxxcbxlucgr490c6nvrvd8rylfocwkpg5AQAAABAAA",
- },
- },
- {
- name: "invalid pattern",
- input: `
- azure:
- azure_key: uie5tff7m5H5lqnqjhaltetqli90a08p6dhv9rn59uo30jgzw8un
- azure_org_id: LOKi
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey.go b/pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey.go
deleted file mode 100644
index e7feda4289ca..000000000000
--- a/pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey.go
+++ /dev/null
@@ -1,163 +0,0 @@
-package azuredirectmanagementkey
-
-import (
- "context"
- "crypto/hmac"
- "crypto/sha512"
- "encoding/base64"
- "errors"
- "fmt"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-const RFC3339WithoutMicroseconds = "2006-01-02T15:04:05"
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- urlPat = regexp.MustCompile(`https://([a-z0-9][a-z0-9-]{0,48}[a-z0-9])\.management\.azure-api\.net`) // https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.APIM.Name/
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure", ".management.azure-api.net"}) + `([a-zA-Z0-9+\/]{83,85}[a-zA-Z0-9]==)`) // pattern for both Primary and secondary key
-
- invalidHosts = simple.NewCache[struct{}]()
- noSuchHostErr = errors.New("no such host")
-)
-
-// 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{".management.azure-api.net"}
-}
-
-// FromData will find and optionally verify Azure Management API keys in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- logger := logContext.AddLogger(ctx).Logger().WithName("azuredirectmanagementkey")
- dataStr := string(data)
-
- urlMatchesUnique := make(map[string]string)
- for _, urlMatch := range urlPat.FindAllStringSubmatch(dataStr, -1) {
- urlMatchesUnique[urlMatch[0]] = urlMatch[1] // urlMatch[0] is the full url, urlMatch[1] is the service name
- }
- keyMatchesUnique := make(map[string]struct{})
- for _, keyMatch := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatchesUnique[strings.TrimSpace(keyMatch[1])] = struct{}{}
- }
-
-EndpointLoop:
- for baseUrl, serviceName := range urlMatchesUnique {
- for key := range keyMatchesUnique {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureDirectManagementKey,
- Raw: []byte(baseUrl),
- RawV2: []byte(baseUrl + ":" + key),
- }
-
- if verify {
- if invalidHosts.Exists(baseUrl) {
- logger.V(3).Info("Skipping invalid registry", "baseUrl", baseUrl)
- continue EndpointLoop
- }
-
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := s.verifyMatch(ctx, client, baseUrl, serviceName, key)
- s1.Verified = isVerified
- if verificationErr != nil {
- if errors.Is(verificationErr, noSuchHostErr) {
- invalidHosts.Set(baseUrl, struct{}{})
- continue EndpointLoop
- }
- s1.SetVerificationError(verificationErr, baseUrl)
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureDirectManagementKey
-}
-
-func (s Scanner) Description() string {
- return "Azure API Management provides a direct management REST API for performing operations on selected entities, such as users, groups, products, and subscriptions."
-}
-
-func (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {
- return false, ""
-}
-
-func (s Scanner) verifyMatch(ctx context.Context, client *http.Client, baseUrl, serviceName, key string) (bool, error) {
- url := fmt.Sprintf(
- "%s/subscriptions/default/resourceGroups/default/providers/Microsoft.ApiManagement/service/%s/apis?api-version=2024-05-01",
- baseUrl, serviceName,
- )
- accessToken, err := generateAccessToken(key)
- if err != nil {
- return false, err
- }
- req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
- if err != nil {
- return false, err
- }
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Authorization", fmt.Sprintf("SharedAccessSignature %s", accessToken))
- resp, err := client.Do(req)
- if err != nil {
- return false, nil
- }
- defer resp.Body.Close()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
- }
-}
-
-// https://learn.microsoft.com/en-us/rest/api/apimanagement/apimanagementrest/azure-api-management-rest-api-authentication
-func generateAccessToken(key string) (string, error) {
- expiry := time.Now().UTC().Add(5 * time.Second).Format(RFC3339WithoutMicroseconds) // expire in 5 seconds
- expiry = expiry + ".0000000Z" // 7 decimals microsecond's precision is must for access token
-
- // Construct the string-to-sign
- stringToSign := fmt.Sprintf("integration\n%s", expiry)
-
- // Generate HMAC-SHA512 signature
- h := hmac.New(sha512.New, []byte(key))
- h.Write([]byte(stringToSign))
- signature := h.Sum(nil)
-
- // Base64 encode the signature
- encodedSignature := base64.StdEncoding.EncodeToString(signature)
-
- // Create the access token
- accessToken := fmt.Sprintf("uid=integration&ex=%s&sn=%s", expiry, encodedSignature)
- return accessToken, nil
-}
diff --git a/pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey_integration_test.go b/pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey_integration_test.go
deleted file mode 100644
index 06afd70bbc65..000000000000
--- a/pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey_integration_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azuredirectmanagementkey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzureDirectManagementAPIKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- url := testSecrets.MustGetField("AZUREDIRECTMANAGEMENTAPI_URL")
- secret := testSecrets.MustGetField("AZUREDIRECTMANAGEMENTAPI_KEY")
- inactiveSecret := testSecrets.MustGetField("AZUREDIRECTMANAGEMENTAPI_KEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte(fmt.Sprintf("You can find a azure management api url %s and key %s within", url, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureDirectManagementKey,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte(fmt.Sprintf("You can find a azure management api url %s and key %s within but not valid", url, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureDirectManagementKey,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AzureDirectManagementAPIKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "Redacted", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AzureDirectManagementAPIKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey_test.go b/pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey_test.go
deleted file mode 100644
index 188585234425..000000000000
--- a/pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package azuredirectmanagementkey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureDirectManagementAPIKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- AZURE_MANGEMENT_API_KEY=UJh1Wn7txjls2GPK1YxO9+3tpqQffSfxb+97PmT8j3cSQoXvGa74lCKpBqPeppTHCharbaMeKqKs/H4gA/go1w==
- AZURE_MANAGEMENT_API_URL=https://trufflesecuritytest.management.azure-api.net
- `,
- want: []string{"https://trufflesecuritytest.management.azure-api.net:UJh1Wn7txjls2GPK1YxO9+3tpqQffSfxb+97PmT8j3cSQoXvGa74lCKpBqPeppTHCharbaMeKqKs/H4gA/go1w=="},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {https://0q66287uqx.management.azure-api.net}
- {AQAAABAAA Ub4yMRDBBdEX/BNyNFM6i6Odj25TB0Zd1BRNx57ZeMGpqzkeokXheNpkkTBtvPQb692id65yc2xLKhZ183rg==}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"https://0q66287uqx.management.azure-api.net:Ub4yMRDBBdEX/BNyNFM6i6Odj25TB0Zd1BRNx57ZeMGpqzkeokXheNpkkTBtvPQb692id65yc2xLKhZ183rg=="},
- },
- {
- name: "invalid pattern",
- input: `
- AZURE_MANGEMENT_API_KEY=UJh1Wn7txjls2GPK1YxO9+3tpqQffSfxb+97PmT8j3cSQoXvGa74lCKp
- AZURE_MANAGEMENT_API_URL=https://trufflesecuritytest.management.azure-api.net
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azurefunctionkey/azurefunctionkey.go b/pkg/detectors/azurefunctionkey/azurefunctionkey.go
deleted file mode 100644
index 99bf43d4ec14..000000000000
--- a/pkg/detectors/azurefunctionkey/azurefunctionkey.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package azurefunctionkey
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure"}) + `\b([a-zA-Z0-9_-]{20,56})\b={0,2}`)
- azureUrlPat = regexp.MustCompile(`\bhttps:\/\/([a-zA-Z0-9-]{2,30})\.azurewebsites\.net\/api\/([a-zA-Z0-9-]{2,30})\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{"azure"}
-}
-
-// FromData will find and optionally verify azure secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- urlMatches := azureUrlPat.FindAllStringSubmatch(dataStr, -1)
- for _, match := range matches {
- resTrim := strings.Split(strings.TrimSpace(match[0]), " ")
- resMatch := resTrim[len(resTrim)-1]
- for _, urlMatch := range urlMatches {
- resUrl := strings.TrimSpace(urlMatch[0])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureFunctionKey,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resUrl),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- req, err := http.NewRequestWithContext(ctx, "GET", resUrl+"?code="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- } else if res.StatusCode == 401 {
- // The secret is determinately not verified (nothing to do)
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureFunctionKey
-}
-
-func (s Scanner) Description() string {
- return "Azure Functions is a serverless compute service that lets you run event-triggered code without having to explicitly provision or manage infrastructure. Azure Function Keys can be used to access and manage these functions."
-}
diff --git a/pkg/detectors/azurefunctionkey/azurefunctionkey_integration_test.go b/pkg/detectors/azurefunctionkey/azurefunctionkey_integration_test.go
deleted file mode 100644
index 2d9881ed72fa..000000000000
--- a/pkg/detectors/azurefunctionkey/azurefunctionkey_integration_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azurefunctionkey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzureFunctionKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AZURE_FUNCTION_KEY")
- inactiveSecret := testSecrets.MustGetField("AZURE_FUNCTION_KEY_INACTIVE")
- url := testSecrets.MustGetField("AZURE_FUNCTION_URL")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s azure url %s", secret, url)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureFunctionKey,
- Verified: true,
- RawV2: []byte(secret + url),
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s azure url %s but not valid", inactiveSecret, url)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureFunctionKey,
- Verified: false,
- RawV2: []byte(inactiveSecret + url),
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s azure url %s", secret, url)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureFunctionKey,
- Verified: false,
- RawV2: []byte(secret + url),
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s azure url %s", secret, url)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureFunctionKey,
- Verified: false,
- RawV2: []byte(secret + url),
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AzureFunctionKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no rawV2 secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AzureFunctionKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azurefunctionkey/azurefunctionkey_test.go b/pkg/detectors/azurefunctionkey/azurefunctionkey_test.go
deleted file mode 100644
index 22abd6cbd236..000000000000
--- a/pkg/detectors/azurefunctionkey/azurefunctionkey_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package azurefunctionkey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureFunctionKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- azure:
- azureURL: https://z1dUSi5T.azurewebsites.net/api/W8anB5J4uQi-v3Dcd6p7ySE0E
- azureFunctionkey: B8sm0KyfL1y8vPH3IDTdefevHBCGK33-=
- `,
- want: []string{
- "azurewebsites.net/api/W8anB5J4uQi-v3Dcd6p7ySE0Ehttps://z1dUSi5T.azurewebsites.net/api/W8anB5J4uQi-v3Dcd6p7ySE0E",
- "B8sm0KyfL1y8vPH3IDTdefevHBCGK33https://z1dUSi5T.azurewebsites.net/api/W8anB5J4uQi-v3Dcd6p7ySE0E",
- },
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {https://yaeuxPA9-H.azurewebsites.net/api/Hwy5K}
- {azure AQAAABAAA Ijbql3DKRyIZNQIddzCYKICr}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"Ijbql3DKRyIZNQIddzCYKICrhttps://yaeuxPA9-H.azurewebsites.net/api/Hwy5K"},
- },
- {
- name: "invalid pattern",
- input: `
- azure:
- azureURL: http://invalid.azurecr.io.azure.com
- azureFunctionkey: BXIMbhBlC3=5hIbqCEKvq7op!V2ZfO0XWbcnasZmPm/AJfQqdcnt/+2Ytxc1hDq1m/
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azuresastoken/azuresastoken.go b/pkg/detectors/azuresastoken/azuresastoken.go
deleted file mode 100644
index d13b6b136286..000000000000
--- a/pkg/detectors/azuresastoken/azuresastoken.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package azuresastoken
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // microsoft storage resource naming rules: https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftstorage:~:text=format%3A%0AVaultName_KeyName_KeyVersion.-,Microsoft.Storage,-Expand%20table
- urlPat = regexp.MustCompile(`https://([a-zA-Z0-9][a-z0-9_-]{1,22}[a-zA-Z0-9])\.blob\.core\.windows\.net/[a-z0-9]([a-z0-9-]{1,61}[a-z0-9])?(?:/[a-zA-Z0-9._-]+)*`)
-
- keyPat = regexp.MustCompile(
- detectors.PrefixRegex([]string{"azure", "sas", "token", "blob", ".blob.core.windows.net"}) +
- `(sp=[racwdli]+&st=\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z&se=\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z(?:&sip=\d{1,3}(?:\.\d{1,3}){3}(?:-\d{1,3}(?:\.\d{1,3}){3})?)?(&spr=https)?(?:,https)?&sv=\d{4}-\d{2}-\d{2}&sr=[bcfso]&sig=[a-zA-Z0-9%]{10,})`)
-
- invalidStorageAccounts = simple.NewCache[struct{}]()
-
- noSuchHostErr = errors.New("no such host")
-)
-
-func (s Scanner) Keywords() []string {
- return []string{
- "azure",
- ".blob.core.windows.net",
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureSasToken
-}
-
-func (s Scanner) Description() string {
- return "An Azure Shared Access Signature (SAS) token is a time-limited, permission-based URL query string that grants secure, granular access to Azure Storage resources (e.g., blobs, containers, files) without exposing account keys."
-}
-
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- logger := logContext.AddLogger(ctx).Logger().WithName("azuresas")
-
- dataStr := string(data)
-
- // deduplicate urlMatches
- urlMatchesUnique := make(map[string]string)
- for _, urlMatch := range urlPat.FindAllStringSubmatch(dataStr, -1) {
- urlMatchesUnique[urlMatch[0]] = urlMatch[1]
- }
-
- // deduplicate keyMatches
- keyMatchesUnique := make(map[string]struct{})
- for _, keyMatch := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatchesUnique[keyMatch[1]] = struct{}{}
- }
-
- // Check results.
-UrlLoop:
- for url, storageAccount := range urlMatchesUnique {
- for key := range keyMatchesUnique {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureSasToken,
- Raw: []byte(url),
- RawV2: []byte(url + key),
- }
-
- if verify {
- if invalidStorageAccounts.Exists(storageAccount) {
- logger.V(3).Info("Skipping invalid storage account", "storage account", storageAccount)
- break
- }
-
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyMatch(ctx, client, url, key, true)
- s1.Verified = isVerified
-
- if verificationErr != nil {
- if errors.Is(verificationErr, noSuchHostErr) {
- invalidStorageAccounts.Set(storageAccount, struct{}{})
- continue UrlLoop
- }
- s1.SetVerificationError(verificationErr, key)
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {
- return false, ""
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, url, key string, retryOn403 bool) (bool, error) {
- urlWithToken := url + "?" + key
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlWithToken, nil)
- if err != nil {
- return false, err
- }
-
- res, err := client.Do(req)
- if err != nil {
- if strings.Contains(err.Error(), "no such host") {
- return false, noSuchHostErr
- }
- return false, err
- }
- defer res.Body.Close()
-
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusForbidden:
- if retryOn403 && strings.Contains(string(bodyBytes), "Signature did not match") {
- // need to add additional query parameters for container urls
- // https://stackoverflow.com/questions/25038429/azure-shared-access-signature-signature-did-not-match
- return verifyMatch(ctx, client, url, key+"&comp=list&restype=container", false)
- }
- if strings.Contains(string(bodyBytes), "AuthorizationFailure") && strings.Contains(key, "&sip=") {
- return false, fmt.Errorf("SAS token is restricted to specific IP addresses")
- }
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
diff --git a/pkg/detectors/azuresastoken/azuresastoken_integration_test.go b/pkg/detectors/azuresastoken/azuresastoken_integration_test.go
deleted file mode 100644
index 39883c9af19d..000000000000
--- a/pkg/detectors/azuresastoken/azuresastoken_integration_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azuresastoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzureSasToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- url := testSecrets.MustGetField("AZURESASTOKEN_URL")
- secret := testSecrets.MustGetField("AZURESASTOKEN")
- inactiveSecret := testSecrets.MustGetField("AZURESASTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure sas url %s and token %s within", url, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureSasToken,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure sas url %s and token %s within but not valid", url, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureSasToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AzureSasToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "Redacted", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AzureSasToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azuresastoken/azuresastoken_test.go b/pkg/detectors/azuresastoken/azuresastoken_test.go
deleted file mode 100644
index fec99debde16..000000000000
--- a/pkg/detectors/azuresastoken/azuresastoken_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package azuresastoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureSASToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- AZURE_BLOB_SAS_TOKEN=sp=r&st=2025-03-04T07:24:52Z&se=2025-04-04T15:24:52Z&spr=https&sv=2022-11-02&sr=c&sig=WSdF9YeZhvrbs%2B%2B1f8ZdDBzEe7fBJ%2BenuaXQ%2BJ9WOw0%3D
- AZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity
- `,
- want: []string{"https://trufflesecurity.blob.core.windows.net/trufflesecuritysp=r&st=2025-03-04T07:24:52Z&se=2025-04-04T15:24:52Z&spr=https&sv=2022-11-02&sr=c&sig=WSdF9YeZhvrbs%2B%2B1f8ZdDBzEe7fBJ%2BenuaXQ%2BJ9WOw0%3D"},
- },
- {
- name: "valid pattern with ip",
- input: `
- AZURE_BLOB_SAS_TOKEN=sp=rcwl&st=2025-03-10T06:58:25Z&se=2025-03-10T14:58:25Z&sip=168.1.6.50&spr=https&sv=2022-11-02&sr=c&sig=c%2BUXo%2FJwf%2FGHomqYaw6tyRykKMaAnyikkf8nS7btD3DYg%3D
- AZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity
- `,
- want: []string{"https://trufflesecurity.blob.core.windows.net/trufflesecuritysp=rcwl&st=2025-03-10T06:58:25Z&se=2025-03-10T14:58:25Z&sip=168.1.6.50&spr=https&sv=2022-11-02&sr=c&sig=c%2BUXo%2FJwf%2FGHomqYaw6tyRykKMaAnyikkf8nS7btD3DYg%3D"},
- },
- {
- name: "valid pattern with ip range",
- input: `
- AZURE_BLOB_SAS_TOKEN=sp=rcwl&st=2025-03-10T06:58:25Z&se=2025-03-10T14:58:25Z&sip=168.1.6.50-168.1.6.80&spr=https&sv=2022-11-02&sr=c&sig=RiA6rO2VwFNZ73trWyY6fsasg0ViUp0k3sDxcl6aA1Rtg%3D
- AZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity
- `,
- want: []string{"https://trufflesecurity.blob.core.windows.net/trufflesecuritysp=rcwl&st=2025-03-10T06:58:25Z&se=2025-03-10T14:58:25Z&sip=168.1.6.50-168.1.6.80&spr=https&sv=2022-11-02&sr=c&sig=RiA6rO2VwFNZ73trWyY6fsasg0ViUp0k3sDxcl6aA1Rtg%3D"},
- },
- {
- name: "valid pattern without https",
- input: `
- AZURE_BLOB_SAS_TOKEN=sp=rcwl&st=2025-03-10T06:58:25Z&se=2025-03-10T14:58:25Z&sv=2022-11-02&sr=c&sig=OYbYoPKW7vVGjFMBu2QDDW%2BlpoShcxawVHR91NQPosY8%3D
- AZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity
- `,
- want: []string{"https://trufflesecurity.blob.core.windows.net/trufflesecuritysp=rcwl&st=2025-03-10T06:58:25Z&se=2025-03-10T14:58:25Z&sv=2022-11-02&sr=c&sig=OYbYoPKW7vVGjFMBu2QDDW%2BlpoShcxawVHR91NQPosY8%3D"},
- },
- {
- name: "valid pattern with blob url",
- input: `
- AZURE_BLOB_SAS_TOKEN=sp=r&st=2025-03-04T07:24:52Z&se=2025-04-04T15:24:52Z&spr=https&sv=2022-11-02&sr=c&sig=WSdF9YeZhvrbs%2B%2B1f8ZdDBzEe7fBJ%2BenuaXQ%2BJ9WOw0%3D
- AZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity/test_blob.txt
- `,
- want: []string{"https://trufflesecurity.blob.core.windows.net/trufflesecurity/test_blob.txtsp=r&st=2025-03-04T07:24:52Z&se=2025-04-04T15:24:52Z&spr=https&sv=2022-11-02&sr=c&sig=WSdF9YeZhvrbs%2B%2B1f8ZdDBzEe7fBJ%2BenuaXQ%2BJ9WOw0%3D"},
- },
- {
- name: "invalid pattern",
- input: `
- AZURE_BLOB_SAS_TOKEN=st=2025-03-04T07:24:52Z&se=2025-04-04T15:24:52Z&spr=https&sv=2022-11-02&sr=c
- AZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/12trufflesecurity
- `,
- want: nil,
- },
- {
- name: "invalid pattern with invalid permission",
- input: `
- AZURE_BLOB_SAS_TOKEN=sp=rqx&st=2025-03-04T07:24:52Z&se=2025-04-04T15:24:52Z&spr=https&sv=2022-11-02&sr=c&sig=WSdF9YeZhvrbs%2B%2B1f8ZdDBzEe7fBJ%2BenuaXQ%2BJ9WOw0%3D
- AZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/12trufflesecurity
- `,
- want: nil,
- },
- {
- name: "invalid pattern with invalid ip",
- input: `
- AZURE_BLOB_SAS_TOKEN=sp=rcwl&st=2025-03-10T06:58:25Z&se=2025-03-10T14:58:25Z&sip=168.1.6&spr=https&sv=2022-11-02&sr=c&sig=c%2BUXo%2FJwf%2FGHomqYaw6tyRykKMaAnyikkf8nS7btD3DYg%3D
- AZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azuresearchadminkey/azuresearchadminkey.go b/pkg/detectors/azuresearchadminkey/azuresearchadminkey.go
deleted file mode 100644
index b62778051072..000000000000
--- a/pkg/detectors/azuresearchadminkey/azuresearchadminkey.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package azuresearchadminkey
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure"}) + `\b([0-9a-zA-Z]{52})\b`)
- servicePat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure"}) + `\b([0-9a-zA-Z]{7,40})\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{"azure"}
-}
-
-// FromData will find and optionally verify AzureSearchAdminKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- serviceMatches := servicePat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, serviceMatch := range serviceMatches {
- resServiceMatch := strings.TrimSpace(serviceMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureSearchAdminKey,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resServiceMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- req, err := http.NewRequestWithContext(ctx, "GET", "https://"+resServiceMatch+".search.windows.net/servicestats?api-version=2023-10-01-Preview", nil)
- if err != nil {
- continue
- }
- req.Header.Add("api-key", resMatch)
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- } else if res.StatusCode == 401 || res.StatusCode == 403 {
- // The secret is determinately not verified (nothing to do)
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureSearchAdminKey
-}
-
-func (s Scanner) Description() string {
- return "Azure Search is a search-as-a-service solution that allows developers to incorporate search capabilities into their applications. Azure Search Admin Keys can be used to manage and query search services."
-}
diff --git a/pkg/detectors/azuresearchadminkey/azuresearchadminkey_integration_test.go b/pkg/detectors/azuresearchadminkey/azuresearchadminkey_integration_test.go
deleted file mode 100644
index c59e16ceede9..000000000000
--- a/pkg/detectors/azuresearchadminkey/azuresearchadminkey_integration_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azuresearchadminkey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzureSearchAdminKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AZURE_SEARCH_ADMIN_KEY")
- inactiveSecret := testSecrets.MustGetField("AZURE_SEARCH_ADMIN_KEY_INACTIVE")
- service := testSecrets.MustGetField("AZURE_SEARCH_ADMIN_KEY_SERVICE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s and azure service %s within", secret, service)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureSearchAdminKey,
- Verified: true,
- RawV2: []byte(secret + service),
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s and azure service %s within but not valid", inactiveSecret, service)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureSearchAdminKey,
- Verified: false,
- RawV2: []byte(inactiveSecret + service),
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s and azure service %s within", secret, service)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureSearchAdminKey,
- Verified: false,
- RawV2: []byte(secret + service),
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s and azure service %s within", secret, service)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureSearchAdminKey,
- Verified: false,
- RawV2: []byte(secret + service),
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AzureSearchAdminKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no rawV2 secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AzureSearchAdminKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azuresearchadminkey/azuresearchadminkey_test.go b/pkg/detectors/azuresearchadminkey/azuresearchadminkey_test.go
deleted file mode 100644
index e2dc172714b0..000000000000
--- a/pkg/detectors/azuresearchadminkey/azuresearchadminkey_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package azuresearchadminkey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureSearchAdminKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- azure:
- azureKey: wRRPyhjv8m6JGRujUUrPKa8d3rJ0mrGAxhmqf3A68OgZmlWUJyma
- azureService: TestingService01
- `,
- want: []string{"wRRPyhjv8m6JGRujUUrPKa8d3rJ0mrGAxhmqf3A68OgZmlWUJymaTestingService01", "wRRPyhjv8m6JGRujUUrPKa8d3rJ0mrGAxhmqf3A68OgZmlWUJymaazureKey"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {azure bhIIhGTLlW7gLxy4rM93gLPaPFwdRajJX}
- {azure AQAAABAAA Pntv3pDD31oczaYT99OanBBZyYlnKGUpQb4WEFnK6uUsKiR0Mc09}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{
- "Pntv3pDD31oczaYT99OanBBZyYlnKGUpQb4WEFnK6uUsKiR0Mc09bhIIhGTLlW7gLxy4rM93gLPaPFwdRajJX",
- "Pntv3pDD31oczaYT99OanBBZyYlnKGUpQb4WEFnK6uUsKiR0Mc09AQAAABAAA",
- },
- },
- {
- name: "invalid pattern",
- input: `
- azure:
- Key: wRRPyhjv8m6JGRujUUr-PK#a8d3rJ0mrGAxhmqf3A68OgZmlWUJyma
- Service: TS01
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/azuresearchquerykey/azuresearchquerykey.go b/pkg/detectors/azuresearchquerykey/azuresearchquerykey.go
deleted file mode 100644
index 34b3fb9d463e..000000000000
--- a/pkg/detectors/azuresearchquerykey/azuresearchquerykey.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package azuresearchquerykey
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure"}) + `\b([0-9a-zA-Z]{52})\b`)
- urlPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure"}) + `https:\/\/([0-9a-z]{5,40})\.search\.windows\.net\/indexes\/([0-9a-z]{5,40})\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{"azure"}
-}
-
-// FromData will find and optionally verify AzureSearchQueryKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- urlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, urlMatch := range urlMatches {
- resTrim := strings.Split(strings.TrimSpace(urlMatch[0]), " ")
- resUrlMatch := resTrim[len(resTrim)-1]
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_AzureSearchQueryKey,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resUrlMatch),
- }
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- req, err := http.NewRequestWithContext(ctx, "GET", resUrlMatch+"/docs/$count?api-version=2023-10-01-Preview", nil)
- if err != nil {
- continue
- }
- req.Header.Add("api-key", resMatch)
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- } else if res.StatusCode == 401 || res.StatusCode == 403 {
- // The secret is determinately not verified (nothing to do)
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_AzureSearchQueryKey
-}
-
-func (s Scanner) Description() string {
- return "Azure Search Query Keys are used to authenticate search requests to Azure Search service. They should be kept confidential to prevent unauthorized access to search indexes and data."
-}
diff --git a/pkg/detectors/azuresearchquerykey/azuresearchquerykey_integration_test.go b/pkg/detectors/azuresearchquerykey/azuresearchquerykey_integration_test.go
deleted file mode 100644
index 1967a9b971aa..000000000000
--- a/pkg/detectors/azuresearchquerykey/azuresearchquerykey_integration_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package azuresearchquerykey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestAzureSearchQueryKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("AZURE_SEARCH_QUERY_KEY")
- inactiveSecret := testSecrets.MustGetField("AZURE_SEARCH_QUERY_KEY_INACTIVE")
- url := testSecrets.MustGetField("AZURE_SEARCH_QUERY_KEY_URL")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s and azure url %s within", secret, url)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureSearchQueryKey,
- Verified: true,
- RawV2: []byte(secret + url),
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s and azure url %s within but not valid", inactiveSecret, url)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureSearchQueryKey,
- Verified: false,
- RawV2: []byte(inactiveSecret + url),
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s and azure url %s within", secret, url)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureSearchQueryKey,
- Verified: false,
- RawV2: []byte(secret + url),
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a azure secret %s and azure url %s within", secret, url)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_AzureSearchQueryKey,
- Verified: false,
- RawV2: []byte(secret + url),
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("AzureSearchQueryKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no rawV2 secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AzureSearchQueryKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/azuresearchquerykey/azuresearchquerykey_test.go b/pkg/detectors/azuresearchquerykey/azuresearchquerykey_test.go
deleted file mode 100644
index e0651ecdaf4a..000000000000
--- a/pkg/detectors/azuresearchquerykey/azuresearchquerykey_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package azuresearchquerykey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestAzureSearchQueryKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- azure:
- azure_url: https://tzyexx2ktdfhha8w1cktqzbrgv37ywtu.search.windows.net/indexes/n81wg81jogjfq93cyxfi67vy2g7vwlcqfgi
- azure_key: OKalbM5EBt5hloqU46phTUCZqvNAlZ4S2Jd2gFUCOQ3HG0vQ2uEp
- `,
- want: []string{"OKalbM5EBt5hloqU46phTUCZqvNAlZ4S2Jd2gFUCOQ3HG0vQ2uEphttps://tzyexx2ktdfhha8w1cktqzbrgv37ywtu.search.windows.net/indexes/n81wg81jogjfq93cyxfi67vy2g7vwlcqfgi"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {azure https://w3fsj4c22rdn7mhkf1yxbt7orrvzd720a.search.windows.net/indexes/5934qi40xctuhmzba7ty}
- {azure AQAAABAAA C3idqCYnGa1cTx7iEFJ684QCbSDcEz1jq4s7iRxDDPWYKoK3h3Lr}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"C3idqCYnGa1cTx7iEFJ684QCbSDcEz1jq4s7iRxDDPWYKoK3h3Lrhttps://w3fsj4c22rdn7mhkf1yxbt7orrvzd720a.search.windows.net/indexes/5934qi40xctuhmzba7ty"},
- },
- {
- name: "invalid pattern",
- input: `
- azure:
- url: http://invalid.azurecr.io.azure.com
- azure_key: BXIMbhBlC3=5hIbqCEKvq7op!V2ZfO0XWbcnasZmPm/AJfQqdcnt/+2Ytxc1hDq1m/
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bannerbear/v1/bannerbear.go b/pkg/detectors/bannerbear/v1/bannerbear.go
deleted file mode 100644
index f23c22e27e55..000000000000
--- a/pkg/detectors/bannerbear/v1/bannerbear.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package bannerbear
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-func (s Scanner) Version() int { return 1 }
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bannerbear"}) + `\b([0-9a-zA-Z]{22}tt)\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{"bannerbear"}
-}
-
-// FromData will find and optionally verify Bannerbear secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Bannerbear,
- Raw: []byte(resMatch),
- ExtraData: map[string]string{
- "version": fmt.Sprintf("%d", s.Version()),
- },
- }
-
- if verify {
- isVerified, verificationErr := verifyBannerBear(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Bannerbear
-}
-
-func (s Scanner) Description() string {
- return "Bannerbear is an API for generating dynamic images, videos, and GIFs. Bannerbear API keys can be used to access and manipulate these resources."
-}
-
-// docs: https://developers.bannerbear.com/
-func verifyBannerBear(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bannerbear.com/v2/auth", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/bannerbear/v1/bannerbear_integration_test.go b/pkg/detectors/bannerbear/v1/bannerbear_integration_test.go
deleted file mode 100644
index c67e040f5401..000000000000
--- a/pkg/detectors/bannerbear/v1/bannerbear_integration_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bannerbear
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBannerbear_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BANNERBEAR")
- inactiveSecret := testSecrets.MustGetField("BANNERBEAR_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bannerbear secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bannerbear,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bannerbear secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bannerbear,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Bannerbear.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
-
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "verificationError", "primarySecret", "ExtraData")
- if diff := cmp.Diff(tt.want, got, ignoreOpts); diff != "" {
- t.Errorf("BannerbearV1.FromData() %s - diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bannerbear/v1/bannerbear_test.go b/pkg/detectors/bannerbear/v1/bannerbear_test.go
deleted file mode 100644
index 009862310559..000000000000
--- a/pkg/detectors/bannerbear/v1/bannerbear_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package bannerbear
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBannerBear_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte("{}")))
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bannerBearToken := "Bearer yvxpthLIcYpZweFpPOVeCOtt"
- req.Header.Set("Authorization", bannerBearToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"yvxpthLIcYpZweFpPOVeCOtt"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bannerbear}
- {bannerbear AQAAABAAA Y5UbXOT1Xh1ZOCxztUvGqltt}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"Y5UbXOT1Xh1ZOCxztUvGqltt"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte("{}")))
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bannerBearToken := "Bearer yvxpthLIcYpZweFpPOVeCOtot"
- req.Header.Set("Authorization", bannerBearToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bannerbear/v2/bannerbear.go b/pkg/detectors/bannerbear/v2/bannerbear.go
deleted file mode 100644
index 3b02654214ff..000000000000
--- a/pkg/detectors/bannerbear/v2/bannerbear.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package bannerbear
-
-import (
- "bytes"
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-func (s Scanner) Version() int { return 2 }
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(bb_(?:pr|ma)_[a-f0-9]{30})\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{"bb_pr_", "bb_ma_"}
-}
-
-// FromData will find and optionally verify Bannerbear secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- uniqueMatches := make(map[string]struct{}, len(matches))
-
- for _, match := range matches {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Bannerbear,
- Raw: []byte(match),
- ExtraData: map[string]string{
- "version": fmt.Sprintf("%d", s.Version()),
- },
- }
-
- if verify {
- isVerified, extraData, verificationErr := s.verifyBannerBear(ctx, client, match)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Bannerbear
-}
-
-func (s Scanner) Description() string {
- return "Bannerbear is an API for generating dynamic images, videos, and GIFs. Bannerbear API keys can be used to access and manipulate these resources."
-}
-
-// docs: https://developers.bannerbear.com/
-func (s Scanner) verifyBannerBear(ctx context.Context, client *http.Client, key string) (bool, map[string]string, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.bannerbear.com/v2/auth", http.NoBody)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- extraData := map[string]string{"version": fmt.Sprintf("%d", s.Version())}
-
- switch resp.StatusCode {
- case http.StatusOK:
- extraData["key_type"] = "Project API Key"
- return true, extraData, nil
- case http.StatusBadRequest:
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, extraData, err
- }
-
- // According to Bannerbear API docs (https://developers.bannerbear.com/#authentication), the /auth endpoint
- // expects us to add a project_id parameter to the payload, when using a Full Access Master API Key.
- // otherwise, it returns a 400 Bad Request with "Error: When using a Master API Key you must set a project_id parameter"
- // Also, when we use a Master API Key with limited access, it returns a 400 Bad Request with "Error: this Master Key is Limited Access only"
- validResponse := bytes.Contains(bodyBytes, []byte("When using a Master API Key")) || bytes.Contains(bodyBytes, []byte("Master Key is Limited Access"))
- if validResponse {
- extraData["key_type"] = "Master API Key"
- return true, extraData, nil
- } else {
- return false, extraData, fmt.Errorf("bad request: %s, body: %s", resp.Status, string(bodyBytes))
- }
- case http.StatusUnauthorized:
- return false, extraData, nil
- default:
- return false, extraData, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/bannerbear/v2/bannerbear_integration_test.go b/pkg/detectors/bannerbear/v2/bannerbear_integration_test.go
deleted file mode 100644
index ef8dd9a4c21f..000000000000
--- a/pkg/detectors/bannerbear/v2/bannerbear_integration_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bannerbear
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBannerbear_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BANNERBEARV2")
- inactiveSecret := testSecrets.MustGetField("BANNERBEARV2_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bannerbear secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bannerbear,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bannerbear secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bannerbear,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Bannerbear.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
-
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "verificationError", "primarySecret", "ExtraData")
- if diff := cmp.Diff(tt.want, got, ignoreOpts); diff != "" {
- t.Errorf("BannerbearV2.FromData() %s - diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bannerbear/v2/bannerbear_test.go b/pkg/detectors/bannerbear/v2/bannerbear_test.go
deleted file mode 100644
index f75714a71753..000000000000
--- a/pkg/detectors/bannerbear/v2/bannerbear_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package bannerbear
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBannerBear_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte("{}")))
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bannerBearToken := "Bearer bb_pr_abcdc2b40ef44abcd8cbf3739aabcd"
- req.Header.Set("Authorization", bannerBearToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"bb_pr_abcdc2b40ef44abcd8cbf3739aabcd"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {}
- {AQAAABAAA bb_ma_900063380acef4c7e24c5bcee8af22}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"bb_ma_900063380acef4c7e24c5bcee8af22"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte("{}")))
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bannerBearToken := "Bearer bb_ma_abcdc2b40ef44abcd8cbf3739aabcq"
- req.Header.Set("Authorization", bannerBearToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/baremetrics/baremetrics.go b/pkg/detectors/baremetrics/baremetrics.go
deleted file mode 100644
index 5359dec8a7ec..000000000000
--- a/pkg/detectors/baremetrics/baremetrics.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package baremetrics
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- /*
- Baremetrics has two type of keys:
- - Sandbox: starts with `sk_`
- - Production: starts with `lk_`
- The length of key is not fixed and can range between 18 to 25 characters.
- */
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"baremetrics"}) + `\b((?:sk|lk)_[a-zA-Z0-9]{18,25})\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{"baremetrics"}
-}
-
-// FromData will find and optionally verify Baremetrics secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Baremetrics,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBaremetrics(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Baremetrics
-}
-
-func (s Scanner) Description() string {
- return "Baremetrics is a subscription analytics and insights tool. Baremetrics API keys can be used to access and analyze subscription data."
-}
-
-// docs: https://developers.baremetrics.com/reference/authentication
-func verifyBaremetrics(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.baremetrics.com/v1/account", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, nil
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/baremetrics/baremetrics_integration_test.go b/pkg/detectors/baremetrics/baremetrics_integration_test.go
deleted file mode 100644
index 87fde4b24191..000000000000
--- a/pkg/detectors/baremetrics/baremetrics_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package baremetrics
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBaremetrics_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BAREMETRICS")
- inactiveSecret := testSecrets.MustGetField("BAREMETRICS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a baremetrics secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Baremetrics,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a baremetrics secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Baremetrics,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Baremetrics.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Baremetrics.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/baremetrics/baremetrics_test.go b/pkg/detectors/baremetrics/baremetrics_test.go
deleted file mode 100644
index 71ae28b90411..000000000000
--- a/pkg/detectors/baremetrics/baremetrics_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package baremetrics
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBareMetrics_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- baremetricsToken := "Bearer sk_nGDJWCkPiFAKE5XFTzUUA"
- req.Header.Set("Authorization", baremetricsToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"sk_nGDJWCkPiFAKE5XFTzUUA"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {baremetrics}
- {baremetrics AQAAABAAA lk_JcWYJEi80ZzQA1nRXD}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"lk_JcWYJEi80ZzQA1nRXD"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- baremetricsToken := "Bearer sk_nGDJWC_io8Q025XFTzUUA"
- req.Header.Set("Authorization", baremetricsToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/beamer/beamer.go b/pkg/detectors/beamer/beamer.go
deleted file mode 100644
index 2a73e5213fdd..000000000000
--- a/pkg/detectors/beamer/beamer.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package beamer
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"beamer"}) + `\b([a-zA-Z0-9_+/]{45}=)`)
-)
-
-// 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{"beamer"}
-}
-
-// FromData will find and optionally verify Beamer secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Beamer,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBeamer(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Beamer
-}
-
-func (s Scanner) Description() string {
- return "Beamer is a user engagement platform that helps you communicate product updates and other important information to your users. Beamer API keys can be used to access and manage this information."
-}
-
-func verifyBeamer(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.getbeamer.com/v0/url", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Beamer-Api-Key", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/beamer/beamer_integration_test.go b/pkg/detectors/beamer/beamer_integration_test.go
deleted file mode 100644
index 2fd40a34aed9..000000000000
--- a/pkg/detectors/beamer/beamer_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package beamer
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBeamer_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BEAMER_TOKEN")
- inactiveSecret := testSecrets.MustGetField("BEAMER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a beamer secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Beamer,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a beamer secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Beamer,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Beamer.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Beamer.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/beamer/beamer_test.go b/pkg/detectors/beamer/beamer_test.go
deleted file mode 100644
index 2a9b710f05c6..000000000000
--- a/pkg/detectors/beamer/beamer_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package beamer
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBeamer_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- req.Header.Set("Beamer-Api-Key", "DyVdf7+cAXw4MH9gT1CPotU31RMl__aLKbrABRWvT7TyO=")
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"DyVdf7+cAXw4MH9gT1CPotU31RMl__aLKbrABRWvT7TyO="},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {beamer}
- {beamer AQAAABAAA _FXYx2kyyNv6n_CBb9LrMHZPXa_S8iaj89zYn9mICmkB4=}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"_FXYx2kyyNv6n_CBb9LrMHZPXa_S8iaj89zYn9mICmkB4="},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- req.Header.Set("Beamer-Api-Key", "DyVdf7%c^AXw4MH9gT1CPotU31RMl__aLKbrABRWvT7TyO")
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/beebole/beebole.go b/pkg/detectors/beebole/beebole.go
deleted file mode 100644
index 65f416e48bdf..000000000000
--- a/pkg/detectors/beebole/beebole.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package beebole
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"beebole"}) + `\b([0-9a-z]{40})\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{"beebole"}
-}
-
-// FromData will find and optionally verify Beebole secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Beebole,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBeebole(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Beebole
-}
-
-func (s Scanner) Description() string {
- return "Beebole is a time tracking and business management tool. Beebole API keys can be used to access and manage time tracking data and other business-related information."
-}
-
-// docs: https://beebole.com/help/api/
-func verifyBeebole(ctx context.Context, client *http.Client, key string) (bool, error) {
- payload := strings.NewReader(`{"service": "custom_field.list"}`)
-
- req, err := http.NewRequestWithContext(ctx, "POST", "https://beebole-apps.com/api/v2", payload)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.SetBasicAuth(key, "x")
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/beebole/beebole_integration_test.go b/pkg/detectors/beebole/beebole_integration_test.go
deleted file mode 100644
index 8dc52fc458b5..000000000000
--- a/pkg/detectors/beebole/beebole_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package beebole
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBeebole_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BEEBOLE")
- inactiveSecret := testSecrets.MustGetField("BEEBOLE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a beebole secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Beebole,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a beebole secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Beebole,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Beebole.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Beebole.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/beebole/beebole_test.go b/pkg/detectors/beebole/beebole_test.go
deleted file mode 100644
index b9f668f06ecb..000000000000
--- a/pkg/detectors/beebole/beebole_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package beebole
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBeeBole_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- beeboleAuth := bn6htprmfpukfalts4muwalxh9j15ucvnrfdme8t
- req.Header.Set("Authorization", "Basic " + beeboleAuth) // beebole authorization header
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"bn6htprmfpukfalts4muwalxh9j15ucvnrfdme8t"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {beebole}
- {beebole AQAAABAAA rtwtgvvvekkik48t08tvf659hvyb5w8u4xnueh3u}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"rtwtgvvvekkik48t08tvf659hvyb5w8u4xnueh3u"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- beeboleAuth := DyVdf7%c^AXw4MH9gT1CPotU31RMl__aLKbrABRWvT7TyO
- req.Header.Set("Authorization", "Basic " + beeboleAuth) // beebole authorization header
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/besnappy/besnappy.go b/pkg/detectors/besnappy/besnappy.go
deleted file mode 100644
index 9634bf98fb0e..000000000000
--- a/pkg/detectors/besnappy/besnappy.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package besnappy
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"besnappy"}) + `\b([a-f0-9]{64})\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{"besnappy"}
-}
-
-// FromData will find and optionally verify Besnappy secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Besnappy,
- Raw: []byte(resMatch),
- }
- if verify {
- isVerified, verificationErr := verifyBesnappy(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Besnappy
-}
-
-func (s Scanner) Description() string {
- return "Besnappy is a customer service platform. The detected key can be used to access Besnappy's API, potentially exposing sensitive customer service data."
-}
-
-// docs: https://github.com/BeSnappy/api-docs
-func verifyBesnappy(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://app.besnappy.com/api/v1/accounts", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.SetBasicAuth(key, "x")
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/besnappy/besnappy_integration_test.go b/pkg/detectors/besnappy/besnappy_integration_test.go
deleted file mode 100644
index bab5c5563160..000000000000
--- a/pkg/detectors/besnappy/besnappy_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package besnappy
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBesnappy_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BESNAPPY")
- inactiveSecret := testSecrets.MustGetField("BESNAPPY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a besnappy secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Besnappy,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a besnappy secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Besnappy,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Besnappy.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Besnappy.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/besnappy/besnappy_test.go b/pkg/detectors/besnappy/besnappy_test.go
deleted file mode 100644
index 0ad2393210c1..000000000000
--- a/pkg/detectors/besnappy/besnappy_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package besnappy
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBeSnappy_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte("{}")))
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- beSnappyToken := f58c5d37d7876d32cfdd823f8fe4ded364a8d483b5dbfadcc55ad801b3be8523
- req.Header.Set("Authorization", "Basic " + beSnappyToken) // authorization header
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"f58c5d37d7876d32cfdd823f8fe4ded364a8d483b5dbfadcc55ad801b3be8523"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {besnappy}
- {besnappy AQAAABAAA da5a2e65d83a40d6cebaac60ef01803f8c1a612baa428992ad4c7301df2759ba}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"da5a2e65d83a40d6cebaac60ef01803f8c1a612baa428992ad4c7301df2759ba"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte("{}")))
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- beSnappyToken := f58c5d37d7876d32cf__f8fe4ded364a8d483b5db+adcc55ad801b3be8523
- req.Header.Set("Authorization", "Basic " + beSnappyToken) // authorization header
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/besttime/besttime.go b/pkg/detectors/besttime/besttime.go
deleted file mode 100644
index 30a51e90f3ef..000000000000
--- a/pkg/detectors/besttime/besttime.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package besttime
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"besttime"}) + `\b(pri_[a-f0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"besttime"}
-}
-
-// FromData will find and optionally verify Besttime secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Besttime,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBesttime(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Besttime
-}
-
-func (s Scanner) Description() string {
- return "Besttime is a service used to predict the best time to visit a place. Besttime API keys can be used to access and utilize this service."
-}
-
-// docs: https://documentation.besttime.app/#api-reference
-func verifyBesttime(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://besttime.app/api/v1/keys/"+key, nil)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, err
- }
- body := string(bodyBytes)
-
- if strings.Contains(body, `"status": "OK"`) {
- return true, nil
- } else if strings.Contains(body, `"message": "Invalid api_key_private`) {
- return false, nil
- }
-
- return false, fmt.Errorf("unexpected response body: %s", body)
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/besttime/besttime_integration_test.go b/pkg/detectors/besttime/besttime_integration_test.go
deleted file mode 100644
index 902377d769fa..000000000000
--- a/pkg/detectors/besttime/besttime_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package besttime
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBesttime_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BESTTIME")
- inactiveSecret := testSecrets.MustGetField("BESTTIME_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a besttime secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Besttime,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a besttime secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Besttime,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Besttime.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Besttime.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/besttime/besttime_test.go b/pkg/detectors/besttime/besttime_test.go
deleted file mode 100644
index 3af4f58f1faa..000000000000
--- a/pkg/detectors/besttime/besttime_test.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package besttime
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBestTime_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/besttime/keys/pri_099889f14d114dfaae476569b395eade"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"pri_099889f14d114dfaae476569b395eade"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {besttime}
- {besttime AQAAABAAA pri_cffe0fa1b281feeb01216ec73e149b00}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"pri_cffe0fa1b281feeb01216ec73e149b00"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/besttime/keys/4K1WTb2ysVeg^jHD*wtwhH68K9MuOjiTtXQCS"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/betterstack/betterstack.go b/pkg/detectors/betterstack/betterstack.go
deleted file mode 100644
index 6eecff1d1bc1..000000000000
--- a/pkg/detectors/betterstack/betterstack.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package betterstack
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"betterstack"}) + `\b([0-9a-zA-Z]{24})\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{"betterstack"}
-}
-
-// FromData will find and optionally verify Betterstack secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BetterStack,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyBetterStack(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BetterStack
-}
-
-func (s Scanner) Description() string {
- return "Betterstack is a monitoring service for uptime and performance of websites and APIs. Betterstack API keys can be used to access and manage these monitoring services."
-}
-
-// docs: https://betterstack.com/docs/uptime/api/list-all-existing-monitors/
-func verifyBetterStack(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://uptime.betterstack.com/api/v2/monitors", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/betterstack/betterstack_integration_test.go b/pkg/detectors/betterstack/betterstack_integration_test.go
deleted file mode 100644
index 4273df1f291c..000000000000
--- a/pkg/detectors/betterstack/betterstack_integration_test.go
+++ /dev/null
@@ -1,128 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package betterstack
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBetterstack_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BETTERSTACK")
- inactiveSecret := testSecrets.MustGetField("BETTERSTACK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a betterstack secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BetterStack,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a betterstack secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BetterStack,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Betterstack.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Betterstack.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/betterstack/betterstack_test.go b/pkg/detectors/betterstack/betterstack_test.go
deleted file mode 100644
index 45ff9579bff9..000000000000
--- a/pkg/detectors/betterstack/betterstack_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package betterstack
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBetterStack_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- req.Header.Set("Authorization", "Bearer " + getbetterStackToken())
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- func getBetterStackToken() string{ return "ntJD0ER8QpuT0O1WqsclApO2" }
- `,
- want: []string{"ntJD0ER8QpuT0O1WqsclApO2"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {betterstack}
- {betterstack AQAAABAAA RtSmhl4GkEcFS84Oyi0zlYbE}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"RtSmhl4GkEcFS84Oyi0zlYbE"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- req.Header.Set("Authorization", "Bearer " + getbetterStackToken())
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- func getBetterStackToken() string{ return "DyntJD0ER8QpuT0O1WqsclApO2" }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/billomat/billomat.go b/pkg/detectors/billomat/billomat.go
deleted file mode 100644
index 6cc495e8b5ff..000000000000
--- a/pkg/detectors/billomat/billomat.go
+++ /dev/null
@@ -1,137 +0,0 @@
-package billomat
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"billomat"}) + `\b([0-9a-z]{4,20})\b`) // the Billomat ID must be between 4 and 20 characters long.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"billomat"}) + `\b([0-9a-f]{32})\b`)
-
- errAccountIDNotFound = errors.New("account id not found")
-)
-
-// 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{"billomat"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Billomat
-}
-
-func (s Scanner) Description() string {
- return "Billomat is an online invoicing software. Billomat API keys can be used to access and manage invoices, clients, and other related data."
-}
-
-// FromData will find and optionally verify Billomat secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueIDs, uniqueAPIKeys = make(map[string]struct{}), make(map[string]struct{})
-
- for _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueIDs[match[1]] = struct{}{}
- }
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueAPIKeys[match[1]] = struct{}{}
- }
-
- for apiKey := range uniqueAPIKeys {
- for id := range uniqueIDs {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Billomat,
- Raw: []byte(apiKey),
- RawV2: []byte(apiKey + id),
- }
-
- if verify {
- isVerified, verificationErr := verifyBillomat(ctx, client, id, apiKey)
- s1.Verified = isVerified
- if verificationErr != nil {
- // remove the account ID if not found to prevent reuse during other API key checks.
- if errors.Is(verificationErr, errAccountIDNotFound) {
- delete(uniqueIDs, id)
- continue
- }
-
- s1.SetVerificationError(verificationErr, apiKey)
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-// docs: https://www.billomat.com/en/api/basics/authentication/
-func verifyBillomat(ctx context.Context, client *http.Client, id, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://%s.billomat.net/api/v2/clients/myself", id), http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-BillomatApiKey", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- case http.StatusNotFound: // billomat api returns 404 if account id does not exist
- // read the full response body
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, nil
- }
-
- /*
- The regex for capturing a Billomat ID is prone to false positives.
- To minimize incorrect matches, we return an error if the captured account ID does not exist,
- as this likely indicates the match was invalid.
- */
- if strings.Contains(string(bodyBytes), "account not found") {
- return false, errAccountIDNotFound
- }
-
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/billomat/billomat_integration_test.go b/pkg/detectors/billomat/billomat_integration_test.go
deleted file mode 100644
index d06708cfa68a..000000000000
--- a/pkg/detectors/billomat/billomat_integration_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package billomat
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBillomat_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BILLOMAT")
- id := testSecrets.MustGetField("BILLOMAT_ID")
- inactiveSecret := testSecrets.MustGetField("BILLOMAT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a billomat secret %s within billomat id %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Billomat,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a billomat secret %s within billomat id %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Billomat,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Billomat.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Billomat.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/billomat/billomat_test.go b/pkg/detectors/billomat/billomat_test.go
deleted file mode 100644
index 0aa00263df0d..000000000000
--- a/pkg/detectors/billomat/billomat_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-package billomat
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBilloMat_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.billomat.net/v2/id/truffletest"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- req.Header.Set("X-BillomatApiKey", "c09761f99f39f79ae28eaaf8df20d7c9")
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
-
- // Check response status
- if resp.StatusCode == http.StatusOK {
- fmt.Println("Request successful!")
- } else {
- fmt.Println("Request failed with status:", resp.Status)
- }
- }`,
- want: []string{
- "c09761f99f39f79ae28eaaf8df20d7c9truffletest",
- },
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {billomat m2o8fqf8}
- {billomat AQAAABAAA 36a584c280b5b617e8eb25dae6b64d63}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"36a584c280b5b617e8eb25dae6b64d63m2o8fqf8"},
- },
- {
- name: "invalid pattern",
- input: `
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
- req.Header.Set("X-BillomatApiKey", "c09761h99f39f79ae28eaaf8df20d7c9")
- billomatID := truffle-test
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bingsubscriptionkey/bingsubscriptionkey.go b/pkg/detectors/bingsubscriptionkey/bingsubscriptionkey.go
deleted file mode 100644
index b6c1e0e71893..000000000000
--- a/pkg/detectors/bingsubscriptionkey/bingsubscriptionkey.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package bingsubscriptionkey
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bing"}) + `\b([a-fA-F0-9]{32})\b`)
-)
-
-func (s Scanner) Keywords() []string {
- return []string{"bing"}
-}
-
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BingSubscriptionKey,
- Raw: []byte(match),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, subscriptionKey string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.bing.microsoft.com/v7.0/search?q=trufflehog", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Ocp-Apim-Subscription-Key", subscriptionKey)
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BingSubscriptionKey
-}
-
-func (s Scanner) Description() string {
- return "Bing Subscription Key is a key used to access the Bing Web Search API."
-}
diff --git a/pkg/detectors/bingsubscriptionkey/bingsubscriptionkey_integration_test.go b/pkg/detectors/bingsubscriptionkey/bingsubscriptionkey_integration_test.go
deleted file mode 100644
index 6d2fbc160076..000000000000
--- a/pkg/detectors/bingsubscriptionkey/bingsubscriptionkey_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bingsubscriptionkey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBingsubscriptionkey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BING_SUBSCRIPTION_KEY")
- inactiveSecret := testSecrets.MustGetField("BING_SUBSCRIPTION_KEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bing subscription key %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BingSubscriptionKey,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bing subscription key %s within but not valid", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BingSubscriptionKey,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the key within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bing subscription key %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BingSubscriptionKey,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bing subscription key %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BingSubscriptionKey,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Bingsubscriptionkey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Bingsubscriptionkey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bingsubscriptionkey/bingsubscriptionkey_test.go b/pkg/detectors/bingsubscriptionkey/bingsubscriptionkey_test.go
deleted file mode 100644
index 52a8a61d1751..000000000000
--- a/pkg/detectors/bingsubscriptionkey/bingsubscriptionkey_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package bingsubscriptionkey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBingsubscriptionkey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.net/v2/api"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // set bing subscription key
- bingKey := "89017d414ed64edb9c776d4a52102b9a"
- req.Header.Set("Ocp-Apim-Subscription-Key", bingKey)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }`,
- want: []string{"89017d414ed64edb9c776d4a52102b9a"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bing}
- {bing AQAAABAAA dB963b030A1DafB02d8299F04A00a306}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"dB963b030A1DafB02d8299F04A00a306"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.net/v2/api"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // set bing subscription key
- bingKey := "89017d414ed64edb9c776d4J52102b9"
- req.Header.Set("Ocp-Apim-Subscription-Key", bingKey)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }`,
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bitbar/bitbar.go b/pkg/detectors/bitbar/bitbar.go
deleted file mode 100644
index 55b80dcb3c61..000000000000
--- a/pkg/detectors/bitbar/bitbar.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package bitbar
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bitbar"}) + `\b([0-9a-zA-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"bitbar"}
-}
-
-// FromData will find and optionally verify Bitbar secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Bitbar,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBitBar(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Bitbar
-}
-
-func (s Scanner) Description() string {
- return "Bitbar provides a cloud-based mobile app testing platform. Bitbar API keys can be used to access and manage testing resources and data."
-}
-
-// docs: https://support.smartbear.com/bitbar/docs/en/use-rest-apis-with-bitbar.html
-func verifyBitBar(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://cloud.bitbar.com/api/me", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.SetBasicAuth(key, "")
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/bitbar/bitbar_integration_test.go b/pkg/detectors/bitbar/bitbar_integration_test.go
deleted file mode 100644
index 743a0369e9c8..000000000000
--- a/pkg/detectors/bitbar/bitbar_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bitbar
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBitbar_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BITBAR")
- inactiveSecret := testSecrets.MustGetField("BITBAR_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bitbar secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bitbar,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bitbar secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bitbar,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Bitbar.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Bitbar.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bitbar/bitbar_test.go b/pkg/detectors/bitbar/bitbar_test.go
deleted file mode 100644
index a598ef4aaf6d..000000000000
--- a/pkg/detectors/bitbar/bitbar_test.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package bitbar
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBitBar_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bitBarSecret := os.GetEnv("BITBAR_SECRET")
- if bitBarSecret == ""{
- bitBarSecret = "64pq66z15thg8fh3acd00l35lpyg7c82"
- }
- req.Header.Set("Authorization", "Basic " + bitBarSecret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"64pq66z15thg8fh3acd00l35lpyg7c82"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bitbar}
- {bitbar AQAAABAAA EJEpftl3MtqwEvE9nwiJhw2rWgjrhP1q}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"EJEpftl3MtqwEvE9nwiJhw2rWgjrhP1q"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bitBarSecret := os.GetEnv("BITBAR_SECRET")
- if bitBarSecret == ""{
- bitBarSecret = "DyV64pq66z15thg8fh3&cd00l35lpyg7c82$"
- }
- req.Header.Set("Authorization", "Basic " + bitBarSecret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bitbucketapppassword/bitbucketapppassword.go b/pkg/detectors/bitbucketapppassword/bitbucketapppassword.go
deleted file mode 100644
index 61e9d2a5ccd7..000000000000
--- a/pkg/detectors/bitbucketapppassword/bitbucketapppassword.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package bitbucketapppassword
-
-import (
- "context"
- "encoding/base64"
- "fmt"
- "io"
- "net/http"
- "regexp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-// Scanner is a stateless struct that implements the detector interface.
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-// Keywords are used for efficiently pre-filtering chunks.
-func (s Scanner) Keywords() []string {
- return []string{"bitbucket", "ATBB"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BitbucketAppPassword
-}
-
-func (s Scanner) Description() string {
- return "Bitbucket is a Git repository hosting service by Atlassian. Bitbucket App Passwords are used to authenticate to the Bitbucket API."
-}
-
-const bitbucketAPIUserURL = "https://api.bitbucket.org/2.0/user"
-
-var (
- defaultClient = common.SaneHttpClient()
-)
-
-var (
- // credentialPatterns uses named capture groups (?P...) for readability and robustness.
- credentialPatterns = []*regexp.Regexp{
- // Explicitly define the boundary as (start of string) or (a non-username character).
- regexp.MustCompile(`(?:^|[^A-Za-z0-9-_])(?P[A-Za-z0-9-_]{1,30}):(?PATBB[A-Za-z0-9_=.-]+)\b`),
- // Catches 'https://username:password@bitbucket.org' pattern
- regexp.MustCompile(`https://(?P[A-Za-z0-9-_]{1,30}):(?PATBB[A-Za-z0-9_=.-]+)@bitbucket\.org`),
- // Catches '("username", "password")' pattern, used for HTTP Basic Auth
- regexp.MustCompile(`"(?P[A-Za-z0-9-_]{1,30})",\s*"(?PATBB[A-Za-z0-9_=.-]+)"`),
- }
-)
-
-// FromData will find and optionally verify Bitbucket App Password secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {
- dataStr := string(data)
-
- uniqueCredentials := make(map[string]string)
-
- for _, pattern := range credentialPatterns {
- for _, match := range pattern.FindAllStringSubmatch(dataStr, -1) {
- // Extract credentials using named capture groups for readability.
- namedMatches := make(map[string]string)
- for i, name := range pattern.SubexpNames() {
- if i != 0 && name != "" {
- namedMatches[name] = match[i]
- }
- }
-
- username := namedMatches["username"]
- password := namedMatches["password"]
-
- if username != "" && password != "" {
- uniqueCredentials[username] = password
- }
- }
- }
-
- var results []detectors.Result
- for username, password := range uniqueCredentials {
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_BitbucketAppPassword,
- Raw: fmt.Appendf(nil, "%s:%s", username, password),
- }
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- var vErr error
- result.Verified, vErr = verifyCredential(ctx, client, username, password)
- if vErr != nil {
- result.SetVerificationError(vErr, username, password)
- }
- }
- results = append(results, result)
- }
-
- return results, nil
-}
-
-// verifyCredential checks if a given username and app password are valid by making a request to the Bitbucket API.
-func verifyCredential(ctx context.Context, client *http.Client, username, password string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, bitbucketAPIUserURL, nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Accept", "application/json")
- auth := base64.StdEncoding.EncodeToString(fmt.Appendf(nil, "%s:%s", username, password))
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", auth))
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK, http.StatusForbidden:
- // A 403 can indicate a valid credential with insufficient scope, which is still a finding.
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
diff --git a/pkg/detectors/bitbucketapppassword/bitbucketapppassword_integration_test.go b/pkg/detectors/bitbucketapppassword/bitbucketapppassword_integration_test.go
deleted file mode 100644
index bcc8b0fb637a..000000000000
--- a/pkg/detectors/bitbucketapppassword/bitbucketapppassword_integration_test.go
+++ /dev/null
@@ -1,103 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bitbucketapppassword
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBitbucketAppPassword_FromData_Integration(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
- defer cancel()
-
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- username := testSecrets.MustGetField("USERNAME")
- validPassword := testSecrets.MustGetField("BITBUCKETAPPPASSWORD")
- invalidPassword := "ATBB123abcDEF456ghiJKL789mnoPQR" // An invalid but correctly formatted password
-
- tests := []struct {
- name string
- input string
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "valid credential",
- input: fmt.Sprintf("https://%s:%s@bitbucket.org", username, validPassword),
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BitbucketAppPassword,
- Verified: true,
- Raw: []byte(fmt.Sprintf("%s:%s", username, validPassword)),
- },
- },
- },
- {
- name: "invalid credential",
- input: fmt.Sprintf("https://%s:%s@bitbucket.org", username, invalidPassword),
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BitbucketAppPassword,
- Verified: false,
- Raw: []byte(fmt.Sprintf("%s:%s", username, invalidPassword)),
- },
- },
- },
- {
- name: "no credential found",
- input: "this string has no credentials",
- want: nil,
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- s := &Scanner{}
- got, err := s.FromData(ctx, true, []byte(tc.input))
-
- if (err != nil) != tc.wantErr {
- t.Fatalf("FromData() error = %v, wantErr %v", err, tc.wantErr)
- }
- // Normalizing results for comparison by removing fields that are not relevant for the test
- for i := range got {
- if got[i].VerificationError() != nil {
- t.Logf("verification error: %s", got[i].VerificationError())
- }
- }
-
- if diff := cmp.Diff(tc.want, got, cmp.Comparer(func(x, y detectors.Result) bool {
- return x.Verified == y.Verified && string(x.Raw) == string(y.Raw) && x.DetectorType == y.DetectorType
- })); diff != "" {
- t.Errorf("FromData() mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := &Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bitbucketapppassword/bitbucketapppassword_test.go b/pkg/detectors/bitbucketapppassword/bitbucketapppassword_test.go
deleted file mode 100644
index b9fa2c213036..000000000000
--- a/pkg/detectors/bitbucketapppassword/bitbucketapppassword_test.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package bitbucketapppassword
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBitbucketAppPassword_FromData(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pair",
- input: `
- [INFO] Sending request to the bitbucket API
- [DEBUG] Using autodesk Key=myuser:ATBB123abcDEF456ghiJKL789mnoPQR
- [INFO] Response received: 200 OK
- `,
- want: []string{"myuser:ATBB123abcDEF456ghiJKL789mnoPQR"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {}
- {AQAAABAAA https://trufflesec:ATBBa9iO-tyg7u_op@bitbucket.org}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"trufflesec:ATBBa9iO-tyg7u_op"},
- },
- {
- name: "valid app password by itself (should not be found)",
- input: "ATBB123abcDEF456ghiJKL789mnoPQR",
- want: []string{},
- },
- {
- name: "pair with invalid username",
- input: "my-very-long-username-that-is-over-thirty-characters:ATBB123abcDEF456ghiJKL789mnoPQR",
- want: []string{},
- },
- {
- name: "url pattern",
- input: `https://anotheruser:ATBB123abcDEF456ghiJKL789mnoPQR@bitbucket.org`,
- want: []string{"anotheruser:ATBB123abcDEF456ghiJKL789mnoPQR"},
- },
- {
- name: "http basic auth pattern",
- input: `("basicauthuser", "ATBB123abcDEF456ghiJKL789mnoPQR")`,
- want: []string{"basicauthuser:ATBB123abcDEF456ghiJKL789mnoPQR"},
- },
- {
- name: "multiple matches",
- input: `user1:ATBB123abcDEF456ghiJKL789mnoPQR and then also user2:ATBBzyxwvUT987srqPON654mlkJIH`,
- want: []string{"user1:ATBB123abcDEF456ghiJKL789mnoPQR", "user2:ATBBzyxwvUT987srqPON654mlkJIH"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bitcoinaverage/bitcoinaverage.go b/pkg/detectors/bitcoinaverage/bitcoinaverage.go
deleted file mode 100644
index 56e531ea8916..000000000000
--- a/pkg/detectors/bitcoinaverage/bitcoinaverage.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package bitcoinaverage
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bitcoinaverage"}) + `\b([a-zA-Z0-9]{43})\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{"bitcoinaverage"}
-}
-
-type response struct {
- Msg string `json:"msg"`
- Success bool `json:"success"`
-}
-
-// FromData will find and optionally verify BitcoinAverage secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BitcoinAverage,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBitcoinAverage(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BitcoinAverage
-}
-
-func (s Scanner) Description() string {
- return "BitcoinAverage is a service that provides cryptocurrency market data. BitcoinAverage API keys can be used to access and retrieve this market data."
-}
-
-// docs: https://apiv2.bitcoinaverage.com/#authentication
-func verifyBitcoinAverage(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://apiv2.bitcoinaverage.com/websocket/v3/get_ticket", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("x-ba-key", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- apiResponse := &response{}
- if err = json.NewDecoder(resp.Body).Decode(apiResponse); err != nil {
- return false, err
- }
-
- if apiResponse.Success {
- return true, nil
- }
-
- return false, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/bitcoinaverage/bitcoinaverage_integration_test.go b/pkg/detectors/bitcoinaverage/bitcoinaverage_integration_test.go
deleted file mode 100644
index 87a78cd52c20..000000000000
--- a/pkg/detectors/bitcoinaverage/bitcoinaverage_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bitcoinaverage
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBitcoinAverage_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BITCOINAVERAGE")
- inactiveSecret := testSecrets.MustGetField("BITCOINAVERAGE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bitcoinaverage secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BitcoinAverage,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bitcoinaverage secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BitcoinAverage,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("BitcoinAverage.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("BitcoinAverage.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bitcoinaverage/bitcoinaverage_test.go b/pkg/detectors/bitcoinaverage/bitcoinaverage_test.go
deleted file mode 100644
index d92960ca6355..000000000000
--- a/pkg/detectors/bitcoinaverage/bitcoinaverage_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-package bitcoinaverage
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBitCoinAverage_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- secret := os.GetEnv("BITCOINAVERAGE")
- if secret == ""{
- // bitcoinaverage secret
- secret = "WZizqeWvRnhZmFlpc5pMc92NP1Du19wxxpd5zjsYY8F"
- }
- req.Header.Set("x-ba-key", secret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"WZizqeWvRnhZmFlpc5pMc92NP1Du19wxxpd5zjsYY8F"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bitcoinaverage}
- {bitcoinaverage AQAAABAAA gVXtVKIj5CO3b0F12XjibnE2TvwS5rL5nJ0kQ2NZkso}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"gVXtVKIj5CO3b0F12XjibnE2TvwS5rL5nJ0kQ2NZkso"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- secret := os.GetEnv("BITCOINAVERAGE")
- if secret == ""{
- // bitcoinaverage secret
- secret = "DyV64pq66z15thg8fh3&cd00l35lpyg7c82$"
- }
- req.Header.Set("x-ba-key", secret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bitfinex/bitfinex.go b/pkg/detectors/bitfinex/bitfinex.go
deleted file mode 100644
index 0f8b0927d2b6..000000000000
--- a/pkg/detectors/bitfinex/bitfinex.go
+++ /dev/null
@@ -1,158 +0,0 @@
-package bitfinex
-
-import (
- "bytes"
- "context"
- "crypto/hmac"
- "crypto/sha512"
- "encoding/hex"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // related resource https://medium.com/@Bitfinex/api-development-update-april-65fe52f84124
- apiKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bitfinex"}) + `\b([A-Za-z0-9_-]{43})\b`)
- apiSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bitfinex"}) + `\b([A-Za-z0-9_-]{43})\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{"bitfinex"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Bitfinex
-}
-
-func (s Scanner) Description() string {
- return "Bitfinex is a cryptocurrency exchange offering various trading options. Bitfinex API keys can be used to access and manage trading accounts."
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
-
- return defaultClient
-}
-
-// FromData will find and optionally verify Bitfinex secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueAPIKeys, uniqueAPISecrets = make(map[string]struct{}), make(map[string]struct{})
-
- for _, apiKey := range apiKeyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueAPIKeys[apiKey[1]] = struct{}{}
- }
-
- for _, apiSecret := range apiSecretPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueAPISecrets[apiSecret[1]] = struct{}{}
- }
-
- for apiKey := range uniqueAPIKeys {
- for apiSecret := range uniqueAPISecrets {
- // as both patterns are same, avoid verifying same string for both
- if apiKey == apiSecret {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Bitfinex,
- Raw: []byte(apiKey),
- }
-
- if verify {
- isVerified, verificationErr := verifyBitfinex(ctx, s.getClient(), apiKey, apiSecret)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-// docs: https://docs.bitfinex.com/docs/introduction
-func verifyBitfinex(ctx context.Context, client *http.Client, apiKey, apiSecret string) (bool, error) {
- baseURL := "https://api.bitfinex.com"
- requestPath := "/v2/auth/r/wallets"
- signaturePath := "/api" + requestPath
- nonce := fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Microsecond))
- body := "{}"
- signaturePayload := signaturePath + nonce + body
- signature, err := sign(signaturePayload, apiSecret)
- if err != nil {
- return false, err
- }
-
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+requestPath, bytes.NewBuffer([]byte(body)))
- if err != nil {
- return false, err
- }
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Accept", "application/json")
- req.Header.Set("bfx-apikey", apiKey)
- req.Header.Set("bfx-signature", signature)
- req.Header.Set("bfx-nonce", nonce)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusInternalServerError:
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, err
- }
-
- if strings.Contains(string(body), "apikey: digest invalid") || strings.Contains(string(body), "apikey: invalid") {
- return false, nil
- } else {
- return false, fmt.Errorf("failed to verify Bitfinex API key, error: %s", string(body))
- }
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
- }
-}
-
-func sign(msg, apiSecret string) (string, error) {
- sig := hmac.New(sha512.New384, []byte(apiSecret))
- _, err := sig.Write([]byte(msg))
- if err != nil {
- return "", nil
- }
- return hex.EncodeToString(sig.Sum(nil)), nil
-}
diff --git a/pkg/detectors/bitfinex/bitfinex_integration_test.go b/pkg/detectors/bitfinex/bitfinex_integration_test.go
deleted file mode 100644
index b663cde92503..000000000000
--- a/pkg/detectors/bitfinex/bitfinex_integration_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bitfinex
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBitfinex_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- apiKey := testSecrets.MustGetField("BITFINEX_API_KEY")
- inactiveApiKey := testSecrets.MustGetField("BITFINEX_API_KEY_INACTIVE")
- apiSecret := testSecrets.MustGetField("BITFINEX_API_SECRET")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bitfinex api key %s within bitfinex api secret %s", apiKey, apiSecret)),
- verify: true,
- },
- want: []detectors.Result{
- // will try to scan (apiKey, apiSecret) which will verify then (apiSecret, apiKey) which will not since both parameters have equal length
- {
- DetectorType: detectorspb.DetectorType_Bitfinex,
- Verified: true,
- },
- {
- DetectorType: detectorspb.DetectorType_Bitfinex,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bitfinex api key %s within bitfinex api secret %s", inactiveApiKey, apiSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bitfinex,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Bitfinex,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Bitfinex.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Bitfinex.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bitfinex/bitfinex_test.go b/pkg/detectors/bitfinex/bitfinex_test.go
deleted file mode 100644
index 29202cb48e2e..000000000000
--- a/pkg/detectors/bitfinex/bitfinex_test.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package bitfinex
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBitFinex_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- bitfinexKey := "HxfuG198amaeCcYkASkto5VuIO-oXcplDV6JZ7OIEQZ"
- bitfinexSecret := "Pf3-3v989gPbJT54D3oDBiFZmJoLpWoTHGvF8xuSBPP"
- http.DefaultClient = client
- c := rest.NewClientWithURL(*api).Credentials(key, secret)
- }
- `,
- want: []string{"HxfuG198amaeCcYkASkto5VuIO-oXcplDV6JZ7OIEQZ", "Pf3-3v989gPbJT54D3oDBiFZmJoLpWoTHGvF8xuSBPP"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bitfinex bdiODwPukXLKUjSLvfeTlKVEwm89zqOhQ2a9chacKcr}
- {bitfinex AQAAABAAA MTvK78juiZmddv3eEyoz1gqRwP89OHreiX6fnXkfbce}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{
- "bdiODwPukXLKUjSLvfeTlKVEwm89zqOhQ2a9chacKcr",
- "MTvK78juiZmddv3eEyoz1gqRwP89OHreiX6fnXkfbce",
- },
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- bitfinexKey := "HxfuG198amaeCcYkASkto5VuIO-oXcplDV6JZ7OIEQZ"
- bitfinexSecret := "kASkto5VuIO%c^HxfuG198amaeCcYkASkto5VuIO"
- http.DefaultClient = client
- c := rest.NewClientWithURL(*api).Credentials(key, secret)
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bitlyaccesstoken/bitlyaccesstoken.go b/pkg/detectors/bitlyaccesstoken/bitlyaccesstoken.go
deleted file mode 100644
index acb2eec3cc07..000000000000
--- a/pkg/detectors/bitlyaccesstoken/bitlyaccesstoken.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package bitlyaccesstoken
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bitly"}) + `\b([a-zA-Z-0-9]{40})\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{"bitly"}
-}
-
-// FromData will find and optionally verify BitLyAccessToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueTokens = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokens[matches[1]] = struct{}{}
- }
-
- for token := range uniqueTokens {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BitLyAccessToken,
- Raw: []byte(token),
- }
-
- if verify {
- isVerified, verificationErr := verifyBitlyAccessToken(ctx, client, token)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BitLyAccessToken
-}
-
-func (s Scanner) Description() string {
- return "Bitly is a URL shortening service. Bitly access tokens can be used to interact with the Bitly API, allowing users to create, manage, and track shortened URLs."
-}
-
-func verifyBitlyAccessToken(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api-ssl.bitly.com/v4/user", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/bitlyaccesstoken/bitlyaccesstoken_integration_test.go b/pkg/detectors/bitlyaccesstoken/bitlyaccesstoken_integration_test.go
deleted file mode 100644
index e4625d2639be..000000000000
--- a/pkg/detectors/bitlyaccesstoken/bitlyaccesstoken_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bitlyaccesstoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBitLyAccessToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BITLYACCESSTOKEN_TOKEN")
- inactiveSecret := testSecrets.MustGetField("BITLYACCESSTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bitlyaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BitLyAccessToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bitlyaccesstoken secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BitLyAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("BitLyAccessToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("BitLyAccessToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bitlyaccesstoken/bitlyaccesstoken_test.go b/pkg/detectors/bitlyaccesstoken/bitlyaccesstoken_test.go
deleted file mode 100644
index a79913f78481..000000000000
--- a/pkg/detectors/bitlyaccesstoken/bitlyaccesstoken_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package bitlyaccesstoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBitlyAccessToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bitlyToken := "2xN7puShxzNf5fZleQthTg305lKr7KrbW95D3gSD"
- req.Header.Set("Authorization", "Bearer " + bitlyToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"2xN7puShxzNf5fZleQthTg305lKr7KrbW95D3gSD"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bitly}
- {bitly AQAAABAAA TKymDGZ62qKyWXsq00Nyp-w1bTJn7bFlXWTaH-2i}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"TKymDGZ62qKyWXsq00Nyp-w1bTJn7bFlXWTaH-2i"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bitlyToken := "2xN7puShxzNf5fZleQthTg305l95D3gSD%c^"
- req.Header.Set("Authorization", "Bearer " + bitlyToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bitmex/bitmex.go b/pkg/detectors/bitmex/bitmex.go
deleted file mode 100644
index 363e5163b607..000000000000
--- a/pkg/detectors/bitmex/bitmex.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package bitmex
-
-import (
- "context"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/hex"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bitmex"}) + `([ \r\n]{1}[0-9a-zA-Z\-\_]{24}[ \r\n]{1})`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bitmex"}) + `([ \r\n]{1}[0-9a-zA-Z\-\_]{48}[ \r\n]{1})`)
-)
-
-// 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{"bitmex"}
-}
-
-// FromData will find and optionally verify Bitmex secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, secretMatch := range secretMatches {
- resSecretMatch := strings.TrimSpace(secretMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Bitmex,
- Raw: []byte(resSecretMatch),
- RawV2: []byte(resMatch + resSecretMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBitmex(ctx, client, resMatch, resSecretMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Bitmex
-}
-
-func (s Scanner) Description() string {
- return "Bitmex is a cryptocurrency exchange and derivative trading platform. Bitmex API keys can be used to access and trade on the platform programmatically."
-}
-
-// docs: https://www.bitmex.com/app/apiKeysUsage
-func verifyBitmex(ctx context.Context, client *http.Client, key, secret string) (bool, error) {
- timestamp := strconv.FormatInt(time.Now().Unix()+5, 10)
- action := "GET"
- path := "/api/v1/user"
- payload := url.Values{}
-
- signature := getBitmexSignature(timestamp, secret, action, path, payload.Encode())
-
- req, err := http.NewRequestWithContext(ctx, action, "https://www.bitmex.com"+path, strings.NewReader(payload.Encode()))
- if err != nil {
- return false, err
- }
-
- req.Header.Add("api-expires", timestamp)
- req.Header.Add("api-key", key)
- req.Header.Add("api-signature", signature)
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-func getBitmexSignature(timeStamp string, secret string, action string, path string, payload string) string {
- mac := hmac.New(sha256.New, []byte(secret))
- mac.Write([]byte(action + path + timeStamp + payload))
- macsum := mac.Sum(nil)
- return hex.EncodeToString(macsum)
-}
diff --git a/pkg/detectors/bitmex/bitmex_integration_test.go b/pkg/detectors/bitmex/bitmex_integration_test.go
deleted file mode 100644
index 4272f4713937..000000000000
--- a/pkg/detectors/bitmex/bitmex_integration_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bitmex
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBitmex_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- key := testSecrets.MustGetField("BITMEX_KEY")
- inactiveKey := testSecrets.MustGetField("BITMEX_KEY_INACTIVE")
- secret := testSecrets.MustGetField("BITMEX")
- inactiveSecret := testSecrets.MustGetField("BITMEX_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bitmex key %s with bitmex secret %s within", key, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bitmex,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bitmex key %s with bitmex secret %s within but not valid", inactiveKey, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bitmex,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Bitmex.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no raw v2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Bitmex.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bitmex/bitmex_test.go b/pkg/detectors/bitmex/bitmex_test.go
deleted file mode 100644
index 209e1afd253b..000000000000
--- a/pkg/detectors/bitmex/bitmex_test.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package bitmex
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBitmex_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bitmexKey := " EPwUIxOIveS463D_2O9LFgkz "
- bitmexSecret := " W_HlMtrmELzXm4bSlWv49JLcgvg5hvu467WbbnpmoEA-RjrY "
-
- signature, err := generateSecretSignature(bitmexKey, bitmexSecret)
- if err != nil{
- return err
- }
-
- req.Header.Set("api-signature", signature)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"EPwUIxOIveS463D_2O9LFgkzW_HlMtrmELzXm4bSlWv49JLcgvg5hvu467WbbnpmoEA-RjrY"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bitmex EPwUIxOIveS463D_2O9LFgkz }
- {bitmex AQAAABAAA W_HlMtrmELzXm4bSlWv49JLcgvg5hvu467WbbnpmoEA-RjrY }
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"EPwUIxOIveS463D_2O9LFgkzW_HlMtrmELzXm4bSlWv49JLcgvg5hvu467WbbnpmoEA-RjrY"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bitmexKey := "mELzXm4bSlWv49JLc%c^"
- bitmexSecret := "IXpH-fJJiLFn--Wo7rnlXE"
-
- signature, err := generateSecretSignature(bitmexKey, bitmexSecret)
- if err != nil{
- return err
- }
-
- req.Header.Set("api-signature", signature)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/blazemeter/blazemeter.go b/pkg/detectors/blazemeter/blazemeter.go
deleted file mode 100644
index dc6df12f4046..000000000000
--- a/pkg/detectors/blazemeter/blazemeter.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package blazemeter
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"blazemeter", "runscope"}) + `\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\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{"blazemeter", "runscope"}
-}
-
-// FromData will find and optionally verify Blazemeter secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Blazemeter,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBlazeMeter(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Blazemeter
-}
-
-func (s Scanner) Description() string {
- return "Blazemeter is a continuous testing platform for DevOps. The keys can be used to access and manage performance tests and other resources."
-}
-
-// docs: https://help.blazemeter.com/apidocs/api-monitoring/account.htm?tocpath=API%20Monitoring%7C_____12
-func verifyBlazeMeter(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.runscope.com/account", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/blazemeter/blazemeter_integration_test.go b/pkg/detectors/blazemeter/blazemeter_integration_test.go
deleted file mode 100644
index 12d958cb23a8..000000000000
--- a/pkg/detectors/blazemeter/blazemeter_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package blazemeter
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBlazemeter_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BLAZEMETER")
- inactiveSecret := testSecrets.MustGetField("BLAZEMETER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a blazemeter secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Blazemeter,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a blazemeter secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Blazemeter,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Blazemeter.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Blazemeter.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/blazemeter/blazemeter_test.go b/pkg/detectors/blazemeter/blazemeter_test.go
deleted file mode 100644
index 2f5d6f45d381..000000000000
--- a/pkg/detectors/blazemeter/blazemeter_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package blazemeter
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBlazeMeter_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- blazemeterToken := "sjbuxa3m-vs4n-ykl8-8jpv-i09hdidciolp"
- req.Header.Set("Authorization", "Bearer " + blazemeterToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"sjbuxa3m-vs4n-ykl8-8jpv-i09hdidciolp"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {runscope}
- {runscope AQAAABAAA vzn9dy84-mnvd-alqd-4pbf-cn618kvo26le}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"vzn9dy84-mnvd-alqd-4pbf-cn618kvo26le"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- blazemeterToken := "sjbuxa3m-vs4n- ykl8-8jpv#i09hdidciolp"
- req.Header.Set("Authorization", "Bearer " + blazemeterToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/blitapp/blitapp.go b/pkg/detectors/blitapp/blitapp.go
deleted file mode 100644
index 21f10e0ab94e..000000000000
--- a/pkg/detectors/blitapp/blitapp.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package blitapp
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"blitapp"}) + `\b([a-zA-Z0-9_-]{39})\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{"blitapp"}
-}
-
-// FromData will find and optionally verify BlitApp secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BlitApp,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBlitApp(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BlitApp
-}
-
-func (s Scanner) Description() string {
- return "BlitApp is a service used for managing applications. BlitApp API keys can be used to access and modify application data."
-}
-
-// docs: https://blitapp.com/api/#/App/get_apps_all
-func verifyBlitApp(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://blitapp.com/api/apps/all", nil)
- if err != nil {
- return false, nil
- }
-
- req.Header.Add("API-Key", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, nil
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/blitapp/blitapp_integration_test.go b/pkg/detectors/blitapp/blitapp_integration_test.go
deleted file mode 100644
index 01e4e9df16a9..000000000000
--- a/pkg/detectors/blitapp/blitapp_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package blitapp
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBlitApp_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BLITAPP")
- inactiveSecret := testSecrets.MustGetField("BLITAPP_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a blitapp secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BlitApp,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a blitapp secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BlitApp,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("BlitApp.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("BlitApp.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/blitapp/blitapp_test.go b/pkg/detectors/blitapp/blitapp_test.go
deleted file mode 100644
index a759f7949e69..000000000000
--- a/pkg/detectors/blitapp/blitapp_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package blitapp
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBlitApp_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- blitAppKey := "I_MncTA8nlFcqlBCakI5vwkwFD4_zRUYZKt8hyd"
- req.Header.Set("API-Key", blitAppKey)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"I_MncTA8nlFcqlBCakI5vwkwFD4_zRUYZKt8hyd"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {blitapp}
- {blitapp AQAAABAAA 188hN_78_V86WbCBVJd6OLMQJTHva7cbSf8HDFo}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"188hN_78_V86WbCBVJd6OLMQJTHva7cbSf8HDFo"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- blitAppKey := "I_Mn%^&*qlBCakI5vwkwFD4_zRUY"
- req.Header.Set("API-Key", blitAppKey)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/blocknative/blocknative.go b/pkg/detectors/blocknative/blocknative.go
deleted file mode 100644
index c9e5e626f8cd..000000000000
--- a/pkg/detectors/blocknative/blocknative.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package blocknative
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"blocknative"}) + `\b([0-9Aa-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\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{"blocknative"}
-}
-
-// FromData will find and optionally verify Blocknative secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BlockNative,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBlocknative(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BlockNative
-}
-
-func (s Scanner) Description() string {
- return "Blocknative is a platform that provides real-time blockchain transaction monitoring and notification services. Blocknative API keys can be used to access and interact with these services."
-}
-
-// docs: https://docs.blocknative.com/gas-prediction/gas-platform#api-endpoint
-func verifyBlocknative(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.blocknative.com/gasprices/blockprices", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- // Right now the blocknative API logic is broken and return 200 for invalid key as well
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusTooManyRequests:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/blocknative/blocknative_integration_test.go b/pkg/detectors/blocknative/blocknative_integration_test.go
deleted file mode 100644
index d781cc699589..000000000000
--- a/pkg/detectors/blocknative/blocknative_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package blocknative
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBlocknative_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BLOCKNATIVE")
- inactiveSecret := testSecrets.MustGetField("BLOCKNATIVE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a blocknative secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BlockNative,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a blocknative secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BlockNative,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Blocknative.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Blocknative.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/blocknative/blocknative_test.go b/pkg/detectors/blocknative/blocknative_test.go
deleted file mode 100644
index 406bacafc1d9..000000000000
--- a/pkg/detectors/blocknative/blocknative_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package blocknative
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBlockNative_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- blocknativeSecret := "76e50995-059f-3d1a-af8e-cc85fc05eb03"
- req.Header.Set("Authorization", blocknativeSecret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"76e50995-059f-3d1a-af8e-cc85fc05eb03"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {blocknative}
- {blocknative AQAAABAAA 7b15f7f8-52a8-849d-384e-20b4c0de82dd}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"7b15f7f8-52a8-849d-384e-20b4c0de82dd"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- blocknativeSecret := "2xN7puShxzNf5fZleQthTg305l95D3gSD%c^"
- req.Header.Set("Authorization", blocknativeSecret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/blogger/blogger.go b/pkg/detectors/blogger/blogger.go
deleted file mode 100644
index ef672bb0e350..000000000000
--- a/pkg/detectors/blogger/blogger.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package blogger
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"blogger"}) + `\b([0-9A-Za-z-]{39})\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{"blogger"}
-}
-
-// FromData will find and optionally verify Blogger secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Blogger,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBlogger(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Blogger
-}
-
-func (s Scanner) Description() string {
- return "Blogger API keys can be used to access and manage blogs on the Blogger platform."
-}
-
-// docs: https://developers.google.com/blogger/docs/3.0/using
-func verifyBlogger(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.googleapis.com/blogger/v3/blogs/2399953?key="+key, nil)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusBadRequest, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/blogger/blogger_integration_test.go b/pkg/detectors/blogger/blogger_integration_test.go
deleted file mode 100644
index 4afd72354d5b..000000000000
--- a/pkg/detectors/blogger/blogger_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package blogger
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBlogger_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BLOGGER")
- inactiveSecret := testSecrets.MustGetField("BLOGGER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a blogger secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Blogger,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a blogger secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Blogger,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Blogger.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Blogger.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/blogger/blogger_test.go b/pkg/detectors/blogger/blogger_test.go
deleted file mode 100644
index c7586c90f6e1..000000000000
--- a/pkg/detectors/blogger/blogger_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package blogger
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBlogger_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", "https://api.example.com/v1/blogger/blogs?key=fnWLw7pz1tc6uCzq6qocQZIxRF6SqUaOOkLqePY", http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
-
- // Check response status
- if resp.StatusCode == http.StatusOK {
- fmt.Println("Request successful!")
- } else {
- fmt.Println("Request failed with status:", resp.Status)
- }
- }
- `,
- want: []string{"fnWLw7pz1tc6uCzq6qocQZIxRF6SqUaOOkLqePY"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {blogger}
- {blogger AQAAABAAA mtkwpygpNROxOgLZCnEvl7gNme1IuFiQm9oxPzJ}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"mtkwpygpNROxOgLZCnEvl7gNme1IuFiQm9oxPzJ"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", "https://api.example.com/v1/blogger/blogs?key=fnWL(w7pz1t)6uCz-q6qocQZIxRF6S/UqePY", http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
-
- // Check response status
- if resp.StatusCode == http.StatusOK {
- fmt.Println("Request successful!")
- } else {
- fmt.Println("Request failed with status:", resp.Status)
- }
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bombbomb/bombbomb.go b/pkg/detectors/bombbomb/bombbomb.go
deleted file mode 100644
index 177520eb376d..000000000000
--- a/pkg/detectors/bombbomb/bombbomb.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package bombbomb
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bombbomb"}) + common.BuildRegexJWT("0,140", "0,419", "0,171"))
-)
-
-// 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{"bombbomb"}
-}
-
-// FromData will find and optionally verify BombBomb secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BombBomb,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBombBomb(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BombBomb
-}
-
-func (s Scanner) Description() string {
- return "BombBomb is a video messaging platform that allows users to create and send video emails. BombBomb API keys can be used to access and manage video email campaigns and contacts."
-}
-
-// docs: https://developer.bombbomb.com/api#operations-Users-UserInfo
-func verifyBombBomb(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bombbomb.com/v2/user/", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", "Bearer "+key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/bombbomb/bombbomb_integration_test.go b/pkg/detectors/bombbomb/bombbomb_integration_test.go
deleted file mode 100644
index cae85980792a..000000000000
--- a/pkg/detectors/bombbomb/bombbomb_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bombbomb
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBombBomb_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BOMBBOMB")
- inactiveSecret := testSecrets.MustGetField("BOMBBOMB_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bombbomb secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BombBomb,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bombbomb secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BombBomb,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("BombBomb.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("BombBomb.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bombbomb/bombbomb_test.go b/pkg/detectors/bombbomb/bombbomb_test.go
deleted file mode 100644
index 00af1e06a729..000000000000
--- a/pkg/detectors/bombbomb/bombbomb_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package bombbomb
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBombBomb_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- bombbombToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
- req.Header.Set("Authorization", bombbombToken)
- `,
- want: []string{"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bombbomb}
- {bombbomb AQAAABAAA eyJioGciOiJIU9I1NiIsInR5cCI6IkpXVCJ9.eyJJdWIiOiIxMjM0NTY3ODkwIiwibmFtZSJ6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5d}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"eyJioGciOiJIU9I1NiIsInR5cCI6IkpXVCJ9.eyJJdWIiOiIxMjM0NTY3ODkwIiwibmFtZSJ6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5d"},
- },
- {
- name: "invalid pattern",
- input: `
- bombbombToken := "eyJhbGciOiJIUzI1N^iIsInRkpXVCJ9.ey$JzdWIiOiIxMjM0NTY3ODkwIiwibmFtZwiaWF0IjoxNTE2MjM5MDIyfQ.S&flKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
- req.Header.Set("Authorization", bombbombToken)
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/boostnote/boostnote.go b/pkg/detectors/boostnote/boostnote.go
deleted file mode 100644
index 085265bd8d90..000000000000
--- a/pkg/detectors/boostnote/boostnote.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package boostnote
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"boostnote"}) + `\b([0-9a-f]{64})\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{"boostnote"}
-}
-
-// FromData will find and optionally verify BoostNote secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BoostNote,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBoostnote(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BoostNote
-}
-
-func (s Scanner) Description() string {
- return "BoostNote is a note-taking application. The secret detected here is likely an API key or token used to access BoostNote services."
-}
-
-// docs: https://boostnote.io/features/public-api
-func verifyBoostnote(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://boostnote.io/api/docs", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/boostnote/boostnote_integration_test.go b/pkg/detectors/boostnote/boostnote_integration_test.go
deleted file mode 100644
index 321b6e4a87a2..000000000000
--- a/pkg/detectors/boostnote/boostnote_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package boostnote
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBoostNote_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BOOSTNOTE")
- inactiveSecret := testSecrets.MustGetField("BOOSTNOTE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a boostnote secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BoostNote,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a boostnote secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BoostNote,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("BoostNote.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("BoostNote.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/boostnote/boostnote_test.go b/pkg/detectors/boostnote/boostnote_test.go
deleted file mode 100644
index 871e2cabf674..000000000000
--- a/pkg/detectors/boostnote/boostnote_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package boostnote
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBoostNote_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- boostnoteKey := "fb1026ac5994e3ad01799fe040289317ba2594a20e9e45307a143be82b49d213"
- req.Header.Set("Authorization", "Bearer " + boostnoteKey)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"fb1026ac5994e3ad01799fe040289317ba2594a20e9e45307a143be82b49d213"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {boostnote}
- {boostnote AQAAABAAA a546e80a8018e1c5e37e4a3366a20aa363489691d2ca335e3a082550d8a92120}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"a546e80a8018e1c5e37e4a3366a20aa363489691d2ca335e3a082550d8a92120"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- boostnoteKey := "#^fb1026ac59=4e3ad01799fe04028931___4a20e9e45307a143be82b49d213$"
- req.Header.Set("Authorization", "Bearer " + boostnoteKey)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/borgbase/borgbase.go b/pkg/detectors/borgbase/borgbase.go
deleted file mode 100644
index f13b947d3a1e..000000000000
--- a/pkg/detectors/borgbase/borgbase.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package borgbase
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"borgbase"}) + `\b([a-zA-Z0-9/_.-]{148,152})\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{"borgbase"}
-}
-
-// FromData will find and optionally verify Borgbase secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Borgbase,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBorgbase(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Borgbase
-}
-
-func (s Scanner) Description() string {
- return "Borgbase is a service for hosting Borg repositories. Borgbase API keys can be used to manage and access these repositories."
-}
-
-// docs: https://docs.borgbase.com/api
-func verifyBorgbase(ctx context.Context, client *http.Client, key string) (bool, error) {
- timeout := 10 * time.Second
- client.Timeout = timeout
-
- payload := strings.NewReader(`{"query":"{ sshList {id, name}}"}`)
-
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.borgbase.com/graphql", payload)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, err
- }
-
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"sshList":[]`)
- if validResponse {
- return true, nil
- }
-
- return false, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/borgbase/borgbase_integration_test.go b/pkg/detectors/borgbase/borgbase_integration_test.go
deleted file mode 100644
index 31757adf83cf..000000000000
--- a/pkg/detectors/borgbase/borgbase_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package borgbase
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBorgbase_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BORGBASE")
- inactiveSecret := testSecrets.MustGetField("BORGBASE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a borgbase secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Borgbase,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a borgbase secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Borgbase,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Borgbase.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Borgbase.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/borgbase/borgbase_test.go b/pkg/detectors/borgbase/borgbase_test.go
deleted file mode 100644
index 603979627532..000000000000
--- a/pkg/detectors/borgbase/borgbase_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package borgbase
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBorgBase_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- payload := '{"query":"{ sshList {id, name}}"}'
- req, err := http.NewRequest("POST", url, payload)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- borgbaseToken := "FoHclCFSi_aV09jowJQ4RUF_MiqW6ioqq6_OcyB0PFlV-mQ1yoFjk5JLlxbzRUzKTA6vsfR8wq6TNc83rtNKlkD092Sj1c9CbPVBXlHksy.sT2I/so6bMGdPcqxzbjrxYgAUiORgqJDeTet4gKOQlZpt"
- req.Header.Set("Authorization", "Bearer " + borgbaseToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"FoHclCFSi_aV09jowJQ4RUF_MiqW6ioqq6_OcyB0PFlV-mQ1yoFjk5JLlxbzRUzKTA6vsfR8wq6TNc83rtNKlkD092Sj1c9CbPVBXlHksy.sT2I/so6bMGdPcqxzbjrxYgAUiORgqJDeTet4gKOQlZpt"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {borgbase}
- {borgbase AQAAABAAA KtSE0ggsVsvvDQPHau2ItXW8yi7YsFTho4wHTTjCDShrWgYA421GzfXMwkOYklS6psQd1W8459NvmcZSmr7_LKqQffBGYAVvexM1D4JxRcQS49H3rnFlwDYspB5_m7AxvmbPrpWj8TfNm7zKCa2Ed}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"KtSE0ggsVsvvDQPHau2ItXW8yi7YsFTho4wHTTjCDShrWgYA421GzfXMwkOYklS6psQd1W8459NvmcZSmr7_LKqQffBGYAVvexM1D4JxRcQS49H3rnFlwDYspB5_m7AxvmbPrpWj8TfNm7zKCa2Ed"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- payload := '{"query":"{ sshList {id, name}}"}'
- req, err := http.NewRequest("POST", url, payload)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- borgbaseToken := "mQ1yoFjk5JLlxbzRUzKTA6vsfR8wq,6TNc83rtNKlkD092Sj1c9CbPVBXlHksy%c^so6bMGdPcqxzbjrxYgAUiORgqJDeTet4gKOQlZpt"
- req.Header.Set("Authorization", "Bearer " + borgbaseToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/box/box.go b/pkg/detectors/box/box.go
deleted file mode 100644
index 0995b7d79db7..000000000000
--- a/pkg/detectors/box/box.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package box
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"box"}) + `\b([0-9a-zA-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"box"}
-}
-
-func (s Scanner) Description() string {
- return "Box is a service offering various service for secure collaboration, content management, and workflow. Box token can be used to access and interact with this data."
-}
-
-// FromData will find and optionally verify Box secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Box,
- Raw: []byte(match),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
- url := "https://api.box.com/2.0/users/me"
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
- if err != nil {
- return false, nil, err
- }
-
- req.Header = http.Header{"Authorization": []string{"Bearer " + token}}
- req.Header.Add("content-type", "application/json")
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- {
- var u user
- if err := json.NewDecoder(res.Body).Decode(&u); err != nil {
- return false, nil, err
- }
- return true, bakeExtraDataFromUser(u), nil
- }
- case http.StatusUnauthorized:
- // 401 access token not found
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Box
-}
-
-func bakeExtraDataFromUser(u user) map[string]string {
- return map[string]string{
- "user_id": u.ID,
- "username": u.Login,
- "user_status": u.Status,
- }
-}
-
-// struct to represent a Box user.
-type user struct {
- ID string `json:"id"`
- Login string `json:"login"`
- Status string `json:"status"`
-}
diff --git a/pkg/detectors/box/box_integration_test.go b/pkg/detectors/box/box_integration_test.go
deleted file mode 100644
index 2c887d56bef1..000000000000
--- a/pkg/detectors/box/box_integration_test.go
+++ /dev/null
@@ -1,165 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package box
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBox_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- token := testSecrets.MustGetField("BOX_ACCESS_TOKEN")
- inactiveToken := testSecrets.MustGetField("BOX_ACCESS_TOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a box token %s within", token)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Box,
- Verified: true,
- Raw: []byte(token),
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a box token %s within but not valid", inactiveToken)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Box,
- Verified: false,
- Raw: []byte(inactiveToken),
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a box token %s within", token)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Box,
- Verified: false,
- Raw: []byte(token),
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a box secret %s within", token)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Box,
- Verified: false,
- Raw: []byte(token),
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Box.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "verificationError", "ExtraData")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Box.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/box/box_test.go b/pkg/detectors/box/box_test.go
deleted file mode 100644
index d4e23d42f7ea..000000000000
--- a/pkg/detectors/box/box_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package box
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBox_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- [INFO] request received to fetch box data
- [INFO] sending API request to box API
- [DEBUG] using Key=Ogowv5cj5AJJjO5F3daNHbKJDdPud0CZ
- [DEBUG] request sent successfully
- [INFO] response received: 200 OK
- [DEBUG] fetch data from the database for ID Qje1HjJmgrNzOQpQZROEeYjmHbD2qdFF
- [INFO] data returned
- `,
- want: []string{"Ogowv5cj5AJJjO5F3daNHbKJDdPud0CZ"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {box}
- {box AQAAABAAA Dxb2zNdFF2QTSMwrZJnoeD54Dc4zZAIW}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"Dxb2zNdFF2QTSMwrZJnoeD54Dc4zZAIW"},
- },
- {
- name: "invalid pattern",
- input: `
- [INFO] request received to fetch box data
- [INFO] sending API request to box API
- [DEBUG] using Key=Ogow-v5cj-5AJJ-jO5F-3daN-HbKJ-DdPu-d0CZ
- [DEBUG] request sent successfully
- [ERROR] response received: 401 UnAuthorized
- [INFO] nothing to return
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/boxoauth/boxoauth.go b/pkg/detectors/boxoauth/boxoauth.go
deleted file mode 100644
index 006a633d1a17..000000000000
--- a/pkg/detectors/boxoauth/boxoauth.go
+++ /dev/null
@@ -1,142 +0,0 @@
-package boxoauth
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- clientIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{"id"}) + `\b([a-zA-Z0-9]{32})\b`)
- clientSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"secret"}) + `\b([a-zA-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"box"}
-}
-
-func (s Scanner) Description() string {
- return "Box is a service offering various service for secure collaboration, content management, and workflow. Box Oauth credentials can be used to access and interact with this data."
-}
-
-// FromData will find and optionally verify Box secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueIdMatches := make(map[string]struct{})
- for _, match := range clientIdPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueIdMatches[match[1]] = struct{}{}
- }
-
- uniqueSecretMatches := make(map[string]struct{})
- for _, match := range clientSecretPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueSecretMatches[match[1]] = struct{}{}
- }
-
- for resIdMatch := range uniqueIdMatches {
- for resSecretMatch := range uniqueSecretMatches {
-
- // ignore if the id and secret are the same
- if resIdMatch == resSecretMatch {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BoxOauth,
- Raw: []byte(resIdMatch),
- RawV2: []byte(resIdMatch + resSecretMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, resIdMatch, resSecretMatch)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, resIdMatch)
- }
-
- results = append(results, s1)
-
- // box client supports only one client id and secret pair
- if s1.Verified {
- break
- }
- }
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, id string, secret string) (bool, map[string]string, error) {
- url := "https://api.box.com/oauth2/token"
- payload := strings.NewReader("grant_type=client_credentials&client_id=" + id + "&client_secret=" + secret)
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, payload)
- if err != nil {
- return false, nil, err
- }
-
- req.Header = http.Header{"content-type": []string{"application/x-www-form-urlencoded"}}
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- // We are using malformed request to check if the client id and secret are valid.
- // In this case, the Box OAuth API returns a 400 status code even if the credentials are valid.
- //
- // - If the client ID/secret are valid, the response contains "unauthorized_client"
- // - If the credentials are invalid, the response contains "invalid_client"
- //
- // So we check the response body for one of these keywords.
- switch res.StatusCode {
- case http.StatusBadRequest:
- {
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, nil, err
- }
- body := string(bodyBytes)
- if strings.Contains(body, "unauthorized_client") {
- return true, nil, nil
- } else if strings.Contains(body, "invalid_client") {
- return false, nil, nil
- } else {
- return false, nil, fmt.Errorf("response body missing expected keyword")
- }
- }
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BoxOauth
-}
diff --git a/pkg/detectors/boxoauth/boxoauth_integration_test.go b/pkg/detectors/boxoauth/boxoauth_integration_test.go
deleted file mode 100644
index e9790ef49224..000000000000
--- a/pkg/detectors/boxoauth/boxoauth_integration_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package boxoauth
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBoxOauth_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- id := testSecrets.MustGetField("BOXOAUTH_ID")
- secret := testSecrets.MustGetField("BOXOAUTH_SECRET")
- invalidSecret := testSecrets.MustGetField("BOXOAUTH_INVALID_SECRET")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a box id %s with secret %s", id, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BoxOauth,
- Verified: true,
- Raw: []byte(id),
- RawV2: []byte(id + secret),
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a box id %s with secret %s", id, invalidSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BoxOauth,
- Verified: false,
- Raw: []byte(id),
- RawV2: []byte(id + invalidSecret),
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("BoxOauth.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
-
- if len(got[i].Raw) == 0 {
- t.Fatalf("no rawV2 secret present: \n %+v", got[i])
- }
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("BoxOauth.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/boxoauth/boxoauth_test.go b/pkg/detectors/boxoauth/boxoauth_test.go
deleted file mode 100644
index 473c609e3581..000000000000
--- a/pkg/detectors/boxoauth/boxoauth_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package boxoauth
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- clientId = common.GenerateRandomPassword(true, true, true, false, 32)
- clientSecret = common.GenerateRandomPassword(true, true, true, false, 32)
- invalidClientSecret = common.GenerateRandomPassword(true, true, true, true, 32)
-)
-
-func TestBoxOauth_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: fmt.Sprintf("box id = '%s' box secret = '%s'", clientId, clientSecret),
- want: []string{clientId + clientSecret},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("box id = '%s' box secret = '%s'", clientId, invalidClientSecret),
- want: nil,
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("box = '%s|%s'", clientId, invalidClientSecret),
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/braintreepayments/braintreepayments.go b/pkg/detectors/braintreepayments/braintreepayments.go
deleted file mode 100644
index fe1b26b0ef84..000000000000
--- a/pkg/detectors/braintreepayments/braintreepayments.go
+++ /dev/null
@@ -1,133 +0,0 @@
-package braintreepayments
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
- useTestURL bool
-}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-const (
- verifyURL = "https://payments.braintree-api.com/graphql"
- verifyTestURL = "https://payments.sandbox.braintree-api.com/graphql"
-)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"braintree"}) + `\b([0-9a-f]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"braintree"}) + `\b([0-9a-z]{16})\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{"braintree"}
-}
-
-// FromData will find and optionally verify BraintreePayments secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
- resIdMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BraintreePayments,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.getClient()
- url := s.getBraintreeURL()
- isVerified, verificationErr := verifyBraintree(ctx, client, url, resIdMatch, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) getBraintreeURL() string {
- if s.useTestURL {
- return verifyTestURL
- }
- return verifyURL
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-func verifyBraintree(ctx context.Context, client *http.Client, url, pubKey, privKey string) (bool, error) {
- payload := strings.NewReader(`{"query": "query { ping }"}`)
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, payload)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Braintree-Version", "2019-01-01")
- req.SetBasicAuth(pubKey, privKey)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- bodyString := string(bodyBytes)
- if !(res.StatusCode == http.StatusOK) {
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-
- validResponse := `"data":{`
- if strings.Contains(bodyString, validResponse) {
- return true, nil
- }
-
- return false, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BraintreePayments
-}
-
-func (s Scanner) Description() string {
- return "Braintree is a full-stack payment platform that makes it easy to accept payments in your mobile app or website. Braintree API keys can be used to access and manage payment transactions, customer data, and other payment-related operations."
-}
diff --git a/pkg/detectors/braintreepayments/braintreepayments_integration_test.go b/pkg/detectors/braintreepayments/braintreepayments_integration_test.go
deleted file mode 100644
index 06c933c8e94c..000000000000
--- a/pkg/detectors/braintreepayments/braintreepayments_integration_test.go
+++ /dev/null
@@ -1,164 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package braintreepayments
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBraintreePayments_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BRAINTREEPAYMENTS")
- id := testSecrets.MustGetField("BRAINTREEPAYMENTS_USER")
- inactiveSecret := testSecrets.MustGetField("BRAINTREEPAYMENTS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{useTestURL: true},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a braintree secret %s within braintree %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BraintreePayments,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{
- client: common.ConstantResponseHttpClient(404, ""),
- },
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a braintree secret %s within braintree %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BraintreePayments,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{
- client: common.SaneHttpClientTimeOut(1 * time.Microsecond),
- },
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a braintree secret %s within braintree %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BraintreePayments,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, unverified",
- s: Scanner{useTestURL: true},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a braintree secret %s within braintree %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BraintreePayments,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("BraintreePayments.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
-
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("BraintreePayments.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/braintreepayments/braintreepayments_test.go b/pkg/detectors/braintreepayments/braintreepayments_test.go
deleted file mode 100644
index 6c5ea244dfa5..000000000000
--- a/pkg/detectors/braintreepayments/braintreepayments_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package braintreepayments
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBrainTreePayments_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- braintreeKey := "f7b3cb83a7fdb915a71ce17ab8a903cc"
- braintreeId := "kmajpm4h1pqoqxyo"
- req.SetBasicAuth(braintreeKey, braintreeId)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"f7b3cb83a7fdb915a71ce17ab8a903cc"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {braintree jvbs4thxyzhh8n00}
- {braintree AQAAABAAA 7d1ab9c76bea2cfb80a29fef8f1e0b12}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"7d1ab9c76bea2cfb80a29fef8f1e0b12"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- braintreeKey := "f7b3cb83a7fdb915a71ce17ab8a903cckmajpm4h1pqoqxyo"
- braintreeId := "kmajpm4h1pqoqxyo"
- req.SetBasicAuth(braintreeKey, braintreeId)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/brandfetch/v1/brandfetch.go b/pkg/detectors/brandfetch/v1/brandfetch.go
deleted file mode 100644
index 8236972bb509..000000000000
--- a/pkg/detectors/brandfetch/v1/brandfetch.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package brandfetch
-
-import (
- "context"
- "net/http"
- "strconv"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- v2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/brandfetch/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-func (s Scanner) Version() int { return 1 }
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
- _ detectors.Versioner = (*Scanner)(nil)
- 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{"brandfetch"}) + `\b([0-9A-Za-z]{40})\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{"brandfetch"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Brandfetch
-}
-
-func (s Scanner) Description() string {
- return "Brandfetch is a service that provides brand data, including logos, colors, fonts, and more. Brandfetch API keys can be used to access this data."
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
-
- return defaultClient
-}
-
-// FromData will find and optionally verify Brandfetch secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueTokenMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokenMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueTokenMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Brandfetch,
- Raw: []byte(match),
- ExtraData: map[string]string{"version": strconv.Itoa(s.Version())},
- }
-
- if verify {
- isVerified, verificationErr := v2.VerifyMatch(ctx, s.getClient(), match)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return
-}
diff --git a/pkg/detectors/brandfetch/v1/brandfetch_integration_test.go b/pkg/detectors/brandfetch/v1/brandfetch_integration_test.go
deleted file mode 100644
index b77f50c0cf7b..000000000000
--- a/pkg/detectors/brandfetch/v1/brandfetch_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package brandfetch
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBrandfetch_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BRANDFETCH")
- inactiveSecret := testSecrets.MustGetField("BRANDFETCH_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a brandfetch secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Brandfetch,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a brandfetch secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Brandfetch,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Brandfetch.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].ExtraData = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Brandfetch.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/brandfetch/v1/brandfetch_test.go b/pkg/detectors/brandfetch/v1/brandfetch_test.go
deleted file mode 100644
index b06e6ea82586..000000000000
--- a/pkg/detectors/brandfetch/v1/brandfetch_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package brandfetch
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBrandFetch_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- brandfetchAPIKey := "uHOAdwfQ7sD2yOpur72UqyUeIqnFwILOIlEPyBtJ"
- req.Header.Set("x-api-key", brandfetchAPIKey) // brandfetch secret
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"uHOAdwfQ7sD2yOpur72UqyUeIqnFwILOIlEPyBtJ"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {uSiXZ-NMpDW-ZJQFSN-5wkT7SqQ8-mDbr9K2pl}
- {brandfetch AQAAABAAA uSiXZNMpDWWhZJQFSNkE5wkT7SqQ8B3mDbr9K2pl}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"uSiXZNMpDWWhZJQFSNkE5wkT7SqQ8B3mDbr9K2pl"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- brandfetchAPIKey := "yUeIqnFwILOIlEPyBt+=JOAdwfQ7sD2uHOAdwf2U[qy]UeIqnFwILOIlEPyBtJ^"
- req.Header.Set("x-api-key", brandfetchAPIKey) // brandfetch secret
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/brandfetch/v2/brandfetch.go b/pkg/detectors/brandfetch/v2/brandfetch.go
deleted file mode 100644
index ce92f93d285e..000000000000
--- a/pkg/detectors/brandfetch/v2/brandfetch.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package brandfetch
-
-import (
- "context"
- "fmt"
- "net/http"
- "strconv"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-func (s Scanner) Version() int { return 2 }
-
-var (
- // Ensure the Scanner satisfies the interface at compile time.
- _ detectors.Detector = (*Scanner)(nil)
- _ detectors.Versioner = (*Scanner)(nil)
- 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{"brandfetch"}) + `([a-zA-Z0-9=+/\-_!@#$%^&*()]{43}=)`)
-)
-
-// 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{"brandfetch"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Brandfetch
-}
-
-func (s Scanner) Description() string {
- return "Brandfetch is a service that provides brand data, including logos, colors, fonts, and more. Brandfetch API keys can be used to access this data."
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
-
- return defaultClient
-}
-
-// FromData will find and optionally verify Brandfetch secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Brandfetch,
- Raw: []byte(match),
- ExtraData: map[string]string{"version": strconv.Itoa(s.Version())},
- }
-
- if verify {
- isVerified, verificationErr := VerifyMatch(ctx, s.getClient(), match)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-// verifyMatch checks if the provided Brandfetch token is valid by making a request to the Brandfetch API.
-// https://docs.brandfetch.com/docs/getting-started
-func VerifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.brandfetch.io/v2/brands/google.com", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", "Bearer "+token)
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer resp.Body.Close()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/brandfetch/v2/brandfetch_integration_test.go b/pkg/detectors/brandfetch/v2/brandfetch_integration_test.go
deleted file mode 100644
index ac1b015b27e2..000000000000
--- a/pkg/detectors/brandfetch/v2/brandfetch_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package brandfetch
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBrandfetch_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BRANDFETCH_V2")
- inactiveSecret := testSecrets.MustGetField("BRANDFETCH_V2_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a brandfetch secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Brandfetch,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a brandfetch secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Brandfetch,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Brandfetch.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].ExtraData = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Brandfetch.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/brandfetch/v2/brandfetch_test.go b/pkg/detectors/brandfetch/v2/brandfetch_test.go
deleted file mode 100644
index e991b6c929cb..000000000000
--- a/pkg/detectors/brandfetch/v2/brandfetch_test.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package brandfetch
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBrandFetch_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: "brandfetch credentials: ZUfake+eKo3qNxLDfake/6vqjOtr4fa6u5wShfakes8=",
- want: []string{"ZUfake+eKo3qNxLDfake/6vqjOtr4fa6u5wShfakes8="},
- },
- {
- name: "valid pattern - assignment format",
- input: "BRANDFETCH_API_KEY=msCwufakeod43s2ad/D0em/LbIBpZqFAKE9P+H3UTno=",
- want: []string{"msCwufakeod43s2ad/D0em/LbIBpZqFAKE9P+H3UTno="},
- },
- {
- name: "valid pattern - complex",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- brandfetchAPIKey := "0mWrufake4X1dRfake0mxS+E48ofakesTlyl55raNOs="
- req.Header.Set("x-api-key", brandfetchAPIKey) // brandfetch secret
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
-
- // Check response status
- if resp.StatusCode == http.StatusOK {
- fmt.Println("Request successful!")
- } else {
- fmt.Println("Request failed with status:", resp.Status)
- }
- }
- `,
- want: []string{"0mWrufake4X1dRfake0mxS+E48ofakesTlyl55raNOs="},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {uSiXZ-NMpDW-ZJQFSN-5wkT7SqQ8-mDbr9K2pl}
- {brandfetch AQAAABAAA 0mWrufake4X1dRfake0mxS+E48ofakesTlyl55rfake=}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"0mWrufake4X1dRfake0mxS+E48ofakesTlyl55rfake="},
- },
- {
- name: "invalid pattern - wrong length",
- input: "brandfetch credentials: yUeIqnFwILOIlEPyBt+=JOAdwfQ7sD2uHOAdwf2U",
- want: nil,
- },
- {
- name: "invalid pattern - invalid characters",
- input: "brandfetch credentials: yUeIqnFwILOIlEPyBt+=JOAdwfQ7sD2uHOAdwf2U[qy]UeIqnFwILOIlEPyBtJ^fakes=",
- want: nil,
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- brandfetchAPIKey := "yUeIqnFwILOIlEPyBt+=JOAdwfQ7sD2uHOAdwf2U[qy]UeIqnFwILOIlEPyBtJ^"
- req.Header.Set("x-api-key", brandfetchAPIKey) // brandfetch secret
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/browserstack/browserstack.go b/pkg/detectors/browserstack/browserstack.go
deleted file mode 100644
index b2ca5beb9a6f..000000000000
--- a/pkg/detectors/browserstack/browserstack.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package browserstack
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "net/http/cookiejar"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
- "golang.org/x/net/publicsuffix"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-const browserStackAPIURL = "https://www.browserstack.com/automate/plan.json"
-
-var (
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"hub-cloud.browserstack.com", "accessKey", "\"access_Key\":", "ACCESS_KEY", "key", "browserstackKey", "BS_AUTHKEY", "BROWSERSTACK_ACCESS_KEY"}) + `\b([0-9a-zA-Z]{20})\b`)
- userPat = regexp.MustCompile(detectors.PrefixRegex([]string{"hub-cloud.browserstack.com", "userName", "\"username\":", "USER_NAME", "user", "browserstackUser", "BS_USERNAME", "BROWSERSTACK_USERNAME"}) + `\b([a-zA-Z\d]{3,18}[._-]*[a-zA-Z\d]{6,11})\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{"browserstack"}
-}
-
-func (s Scanner) getClient(cookieJar *cookiejar.Jar) *http.Client {
- if s.client != nil {
- s.client.Jar = cookieJar
- return s.client
- }
- // Using custom HTTP client instead of common.SaneHttpClient() here because, for unknown reasons, browserstack blocks those requests even with cookie jar attached
- return &http.Client{
- Jar: cookieJar,
- }
-}
-
-// FromData will find and optionally verify BrowserStack secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- userMatches := userPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, userMatch := range userMatches {
-
- resUserMatch := strings.TrimSpace(userMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BrowserStack,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resUserMatch),
- }
-
- if verify {
- // browserstack (via cloudflare) requires cookies to be enabled
- jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
- if err != nil {
- return nil, err
- }
- client := s.getClient(jar)
-
- isVerified, verificationErr := verifyBrowserStackCredentials(ctx, client, resUserMatch, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyBrowserStackCredentials(ctx context.Context, client *http.Client, username, accessKey string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, browserStackAPIURL, nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("User-Agent", common.UserAgent())
-
- req.SetBasicAuth(username, accessKey)
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- if res.StatusCode == http.StatusOK {
- return true, nil
- } else if res.StatusCode == http.StatusForbidden {
- // Sometimes browserstack (via Cloudflare) will block requests for security
- body, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
- if strings.Contains(string(body), "blocked") {
- return false, fmt.Errorf("blocked by browserstack")
- }
- } else if res.StatusCode != http.StatusUnauthorized {
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-
- return false, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BrowserStack
-}
-
-func (s Scanner) Description() string {
- return "BrowserStack is a cloud web and mobile testing platform. BrowserStack credentials can be used to access and manage testing environments."
-}
diff --git a/pkg/detectors/browserstack/browserstack_integration_test.go b/pkg/detectors/browserstack/browserstack_integration_test.go
deleted file mode 100644
index ebe47df316ff..000000000000
--- a/pkg/detectors/browserstack/browserstack_integration_test.go
+++ /dev/null
@@ -1,194 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package browserstack
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBrowserStack_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secretUser := testSecrets.MustGetField("BROWSERSTACK_USER")
- secret := testSecrets.MustGetField("BROWSERSTACK")
- inactiveSecret := testSecrets.MustGetField("BROWSERSTACK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a BROWSERSTACK_ACCESS_KEY %s within BROWSERSTACK_USERNAME %s", secret, secretUser)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BrowserStack,
- Verified: true,
- RawV2: []byte(fmt.Sprintf("%s%s", secret, secretUser)),
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a BROWSERSTACK_ACCESS_KEY %s within BROWSERSTACK_USERNAME %s but not valid", inactiveSecret, secretUser)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BrowserStack,
- Verified: false,
- RawV2: []byte(fmt.Sprintf("%s%s", inactiveSecret, secretUser)),
- },
- },
- wantErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a BROWSERSTACK_ACCESS_KEY %s within BROWSERSTACK_USERNAME %s", secret, secretUser)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_BrowserStack,
- RawV2: []byte(fmt.Sprintf("%s%s", secret, secretUser)),
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("context deadline exceeded"), secret)
- results := []detectors.Result{r}
- return results
- }(),
- wantErr: false,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a BROWSERSTACK_ACCESS_KEY %s within BROWSERSTACK_USERNAME %s", secret, secretUser)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_BrowserStack,
- Verified: false,
- RawV2: []byte(fmt.Sprintf("%s%s", secret, secretUser)),
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 404"), secret)
- results := []detectors.Result{r}
- return results
- }(),
- wantErr: false,
- },
- {
- name: "found, verified but blocked by browserstack",
- s: Scanner{client: common.ConstantResponseHttpClient(403, "blocked")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a BROWSERSTACK_ACCESS_KEY %s within BROWSERSTACK_USERNAME %s", secret, secretUser)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_BrowserStack,
- Verified: false,
- RawV2: []byte(fmt.Sprintf("%s%s", secret, secretUser)),
- }
- r.SetVerificationError(fmt.Errorf("blocked by browserstack"), secret)
- results := []detectors.Result{r}
- return results
- }(),
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("BrowserStack.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", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("BrowserStack.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/browserstack/browserstack_test.go b/pkg/detectors/browserstack/browserstack_test.go
deleted file mode 100644
index 97478c7509ea..000000000000
--- a/pkg/detectors/browserstack/browserstack_test.go
+++ /dev/null
@@ -1,137 +0,0 @@
-package browserstack
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBrowserStack_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- if browserstackKey, _ := os.GetEnv("ACCESS_KEY"); browserstackKey != "cK1bq7JREJtMf1meaGgs" {
- return fmt.Errorf("invalid accessKey: %v expected: %v", browserstackKey, "1YZazUAPFOiaIFljWDhC")
- }
-
- if browserstackUser, _ := os.GetEnv("USER_NAME"); browserstackUser != "truffle-security91" {
- return fmt.Errorf("invalid userName: %v", "truffle-security91")
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{
- "cK1bq7JREJtMf1meaGgstruffle-security91",
- "1YZazUAPFOiaIFljWDhCbrowserstackUser",
- "1YZazUAPFOiaIFljWDhCtruffle-security91",
- "cK1bq7JREJtMf1meaGgsbrowserstackUser",
- },
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {BS_USERNAME Q8fo0ADq_-_Cj4HtE4Gr}
- {BROWSERSTACK_ACCESS_KEY AQAAABAAA 25IQfQKfEm26vKV96nao}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"25IQfQKfEm26vKV96naoQ8fo0ADq_-_Cj4HtE4Gr"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- if browserstackKey, _ := os.GetEnv("ACCESS_KEY"); browserstackKey != "RxLVnOlvj3#V4bh4RBwOd" {
- return fmt.Errorf("invalid accessKey: %v expected: %v", browserstackKey, "RxLVnOlvj3#V4bh4RBwOd")
- }
-
- if browserstackUser, _ := os.GetEnv("USER_NAME"); browserstackUser != "test" {
- return fmt.Errorf("invalid userName: %v", browserstackUser)
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/browshot/browshot.go b/pkg/detectors/browshot/browshot.go
deleted file mode 100644
index c6a3e0b33de8..000000000000
--- a/pkg/detectors/browshot/browshot.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package browshot
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"browshot"}) + `\b([a-zA-Z-0-9]{28})\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{"browshot"}
-}
-
-// FromData will find and optionally verify Browshot secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Browshot,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBrowshot(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Browshot
-}
-
-func (s Scanner) Description() string {
- return "Browshot is a service that allows you to take screenshots of web pages from different browsers and devices. Browshot API keys can be used to automate and manage these screenshots."
-}
-
-// docs: https://browshot.com/api/documentation#instance_list
-func verifyBrowshot(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.browshot.com/api/v1/instance/list?key="+key, nil)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusBadRequest, http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/browshot/browshot_integration_test.go b/pkg/detectors/browshot/browshot_integration_test.go
deleted file mode 100644
index 401ed17470eb..000000000000
--- a/pkg/detectors/browshot/browshot_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package browshot
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBrowshot_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BROWSHOT")
- inactiveSecret := testSecrets.MustGetField("BROWSHOT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a browshot secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Browshot,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a browshot secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Browshot,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Browshot.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Browshot.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/browshot/browshot_test.go b/pkg/detectors/browshot/browshot_test.go
deleted file mode 100644
index 16ba249ba819..000000000000
--- a/pkg/detectors/browshot/browshot_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package browshot
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBrowShot_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.browshot.com/v1/instances/list?key=AemQ06R35S1Y8rXnOzYvT8I4-a7u"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
-
- // Check response status
- if resp.StatusCode == http.StatusOK {
- fmt.Println("Request successful!")
- } else {
- fmt.Println("Request failed with status:", resp.Status)
- }
- }
- `,
- want: []string{"AemQ06R35S1Y8rXnOzYvT8I4-a7u"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {browshot}
- {browshot AQAAABAAA SyGuw6JXLULnOEDZjiicnTtQ4FA3}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"SyGuw6JXLULnOEDZjiicnTtQ4FA3"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.browshot.com/v1/instances/list?key=2xN7puShxzNf5fZleQt#hTg305l95D3gSD-c^"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bscscan/bscscan.go b/pkg/detectors/bscscan/bscscan.go
deleted file mode 100644
index f72b477a1c18..000000000000
--- a/pkg/detectors/bscscan/bscscan.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package bscscan
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bscscan"}) + `\b([0-9A-Z]{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{"bscscan"}
-}
-
-// FromData will find and optionally verify Bscscan secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BscScan,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBscScan(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BscScan
-}
-
-func (s Scanner) Description() string {
- return "BscScan is a block explorer and analytics platform for Binance Smart Chain. BscScan API keys can be used to access data from the Binance Smart Chain blockchain."
-}
-
-// docs: https://docs.bscscan.com/api-endpoints/accounts
-func verifyBscScan(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bscscan.com/api?module=account&action=balance&address=0x70F657164e5b75689b64B7fd1fA275F334f28e18&apikey="+key, nil)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, err
- }
-
- body := string(bodyBytes)
-
- if !strings.Contains(body, "NOTOK") {
- return true, nil
- }
-
- return false, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/bscscan/bscscan_integration_test.go b/pkg/detectors/bscscan/bscscan_integration_test.go
deleted file mode 100644
index b8bacbc2969f..000000000000
--- a/pkg/detectors/bscscan/bscscan_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bscscan
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBscscan_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BSCSCAN")
- inactiveSecret := testSecrets.MustGetField("BSCSCAN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bscscan secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BscScan,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bscscan secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BscScan,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Bscscan.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Bscscan.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bscscan/bscscan_test.go b/pkg/detectors/bscscan/bscscan_test.go
deleted file mode 100644
index e5649eab933a..000000000000
--- a/pkg/detectors/bscscan/bscscan_test.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package bscscan
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBscScan_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.bscscan.com/v1/resource?apikey=HYZHPP4PBYXCOZAVK4FH55W4MRHYLALPU1"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"HYZHPP4PBYXCOZAVK4FH55W4MRHYLALPU1"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bscscan}
- {bscscan AQAAABAAA SLQOD6LO36MN446N44L98FDJR1AS5PYPTR}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"SLQOD6LO36MN446N44L98FDJR1AS5PYPTR"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.bscscan.com/v1/resource?apikey=2xHYZHPP4PBYXCOZAVK4FH55W4MRHYLALPU1thTg303gSD%c^"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/buddyns/buddyns.go b/pkg/detectors/buddyns/buddyns.go
deleted file mode 100644
index e6e0d2b72fc0..000000000000
--- a/pkg/detectors/buddyns/buddyns.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package buddyns
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"buddyns"}) + `\b([0-9a-z]{40})\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{"buddyns"}
-}
-
-// FromData will find and optionally verify Buddyns secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_BuddyNS,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBuddyns(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_BuddyNS
-}
-
-func (s Scanner) Description() string {
- return "BuddyNS is a DNS hosting service. BuddyNS API keys can be used to manage DNS zones and records."
-}
-
-// docs: https://www.buddyns.com/support/api/v2/
-func verifyBuddyns(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.buddyns.com/api/v2/zone/", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Token %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/buddyns/buddyns_integration_test.go b/pkg/detectors/buddyns/buddyns_integration_test.go
deleted file mode 100644
index d1d84bf07093..000000000000
--- a/pkg/detectors/buddyns/buddyns_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package buddyns
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBuddyns_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BUDDYNS")
- inactiveSecret := testSecrets.MustGetField("BUDDYNS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a buddyns secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BuddyNS,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a buddyns secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_BuddyNS,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Buddyns.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Buddyns.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/buddyns/buddyns_test.go b/pkg/detectors/buddyns/buddyns_test.go
deleted file mode 100644
index 078d9f7d6644..000000000000
--- a/pkg/detectors/buddyns/buddyns_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package buddyns
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBuddyNs_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- buddynsToken := "kkmvdiolccw4v0tue4lu7l7kmnnb4ao8z25ezink"
- req.Header.Set("Authorization", "Token " + buddynsToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"kkmvdiolccw4v0tue4lu7l7kmnnb4ao8z25ezink"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {buddyns}
- {buddyns AQAAABAAA jqcayapqh1soy2zlfdbs1j4ytn0mpgmeffzsu2yt}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"jqcayapqh1soy2zlfdbs1j4ytn0mpgmeffzsu2yt"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- buddynsToken := "diolccw4v0tue4lu7l7kmnnb4ao8z25ezink305l95D3gSD%c^"
- req.Header.Set("Authorization", "Token " + buddynsToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/budibase/budibase.go b/pkg/detectors/budibase/budibase.go
deleted file mode 100644
index b37f802b0f2f..000000000000
--- a/pkg/detectors/budibase/budibase.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package budibase
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"budibase"}) + `\b([a-f0-9]{32}-[a-f0-9]{78,80})\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{"budibase"}
-}
-
-// FromData will find and optionally verify Budibase secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Budibase,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyBudibase(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Budibase
-}
-
-func (s Scanner) Description() string {
- return "Budibase is a low-code platform for creating internal tools. Budibase API keys can be used to access and modify applications and data within the platform."
-}
-
-// docs: https://docs.budibase.com/docs/rest
-func verifyBudibase(ctx context.Context, client *http.Client, key string) (bool, error) {
- // URL: https://docs.budibase.com/reference/appsearch
- // API searches for the app with given name, since we only need to check api key, sending any appname will work.
- payload := strings.NewReader(`{"name":"qwerty"}`)
-
- req, err := http.NewRequestWithContext(ctx, "POST", "https://budibase.app/api/public/v1/applications/search", payload)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("x-budibase-api-key", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/budibase/budibase_integration_test.go b/pkg/detectors/budibase/budibase_integration_test.go
deleted file mode 100644
index 40eead4b000a..000000000000
--- a/pkg/detectors/budibase/budibase_integration_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package budibase
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBudibase_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BUDIBASE")
- inactiveSecret := testSecrets.MustGetField("BUDIBASE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a budibase secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Budibase,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a budibase secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Budibase,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 403"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Budibase.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.want[i].VerificationError(), got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Budibase.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/budibase/budibase_test.go b/pkg/detectors/budibase/budibase_test.go
deleted file mode 100644
index e5aa858a833c..000000000000
--- a/pkg/detectors/budibase/budibase_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package budibase
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBudiBase_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- req.Header.Set("x-budibase-api-key", "b256def166fcdf4a429a1e83175105d5-fd36f3da1e934bf533cd0e68dbb80ed6a42e1178bd4200428d83e876e7d05e40b21e3a68888f826d")
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"b256def166fcdf4a429a1e83175105d5-fd36f3da1e934bf533cd0e68dbb80ed6a42e1178bd4200428d83e876e7d05e40b21e3a68888f826d"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {budibase}
- {budibase AQAAABAAA eb72aa19dafbd0166e16299e0bea6a35-96ab88e1b2691be47aa15b343e8e2b5a3be0564b704db9f2812b6e4decde312038c2f3ba00102e}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"eb72aa19dafbd0166e16299e0bea6a35-96ab88e1b2691be47aa15b343e8e2b5a3be0564b704db9f2812b6e4decde312038c2f3ba00102e"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- req.Header.Set("x-budibase-api-key", "diolccw4v0tue4lu7l7kmnnb4ao8z25ezink305l95D3gSD%c^")
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bugherd/bugherd.go b/pkg/detectors/bugherd/bugherd.go
deleted file mode 100644
index 3544e784ba27..000000000000
--- a/pkg/detectors/bugherd/bugherd.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package bugherd
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bugherd"}) + `\b([0-9a-z]{22})\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{"bugherd"}
-}
-
-// FromData will find and optionally verify Bugherd secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Bugherd,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBugherd(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Bugherd
-}
-
-func (s Scanner) Description() string {
- return "Bugherd is a visual feedback and bug tracking tool for websites. Bugherd API keys can be used to access and manage projects, tasks, and feedback data."
-}
-
-// docs: https://www.bugherd.com/api_v2
-func verifyBugherd(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.bugherd.com/api_v2/projects.json", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.SetBasicAuth(key, "x")
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/bugherd/bugherd_integration_test.go b/pkg/detectors/bugherd/bugherd_integration_test.go
deleted file mode 100644
index 7d015a448d68..000000000000
--- a/pkg/detectors/bugherd/bugherd_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bugherd
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBugherd_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BUGHERD")
- inactiveSecret := testSecrets.MustGetField("BUGHERD_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bugherd secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bugherd,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bugherd secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bugherd,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Bugherd.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Bugherd.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bugherd/bugherd_test.go b/pkg/detectors/bugherd/bugherd_test.go
deleted file mode 100644
index c6088709adde..000000000000
--- a/pkg/detectors/bugherd/bugherd_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package bugherd
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBugHerd_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bugherdToken := "fisy6bbu6il4x96bekx587"
- req.Header.Set("Authorization", "Basic " + buddynsToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"fisy6bbu6il4x96bekx587"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bugherd}
- {bugherd AQAAABAAA mx2rxr8ztizo8kytvx1kan}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"mx2rxr8ztizo8kytvx1kan"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bugherdToken := "fisy6bbu+6il4x()96bekx587-7l7kmnnb4ao8z25ezink305l95D3gSD%c^"
- req.Header.Set("Authorization", "Basic " + buddynsToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bugsnag/bugsnag.go b/pkg/detectors/bugsnag/bugsnag.go
deleted file mode 100644
index 752d6fb73e5c..000000000000
--- a/pkg/detectors/bugsnag/bugsnag.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package bugsnag
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bugsnag"}) + `\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\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{"bugsnag"}
-}
-
-// FromData will find and optionally verify Bugsnag secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Bugsnag,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBugsnag(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Bugsnag
-}
-
-func (s Scanner) Description() string {
- return "Bugsnag is an error monitoring service for web and mobile applications. Bugsnag API keys can be used to report and manage errors."
-}
-
-// docs: https://docs.bugsnag.com/api/
-func verifyBugsnag(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bugsnag.com/user/organizations?admin", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("token %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/bugsnag/bugsnag_integration_test.go b/pkg/detectors/bugsnag/bugsnag_integration_test.go
deleted file mode 100644
index 2d56674eb5b8..000000000000
--- a/pkg/detectors/bugsnag/bugsnag_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bugsnag
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBugsnag_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BUGSNAG")
- inactiveSecret := testSecrets.MustGetField("BUGSNAG_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bugsnag secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bugsnag,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bugsnag secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bugsnag,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Bugsnag.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Bugsnag.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bugsnag/bugsnag_test.go b/pkg/detectors/bugsnag/bugsnag_test.go
deleted file mode 100644
index 7b31971b201c..000000000000
--- a/pkg/detectors/bugsnag/bugsnag_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package bugsnag
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBugSnag_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bugsnagToken := "wz9450iu-iewm-jonx-eab8-0ibxwadddm8i"
- req.Header.Set("Authorization", "token " + bugsnagToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"wz9450iu-iewm-jonx-eab8-0ibxwadddm8i"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bugsnag}
- {bugsnag AQAAABAAA heatep16-k3fw-dflj-ucc1-ay1lu0in3p7k}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"heatep16-k3fw-dflj-ucc1-ay1lu0in3p7k"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bugsnagToken := "%c^wz9450iu-iewm-jonx-eab8-"
- req.Header.Set("Authorization", "token " + bugsnagToken)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/buildkite/v1/buildkite.go b/pkg/detectors/buildkite/v1/buildkite.go
deleted file mode 100644
index 7d4497250fcd..000000000000
--- a/pkg/detectors/buildkite/v1/buildkite.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package buildkite
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-type APIResponse struct {
- Scopes []string `json:"scopes"`
-}
-
-func (s Scanner) Version() int { return 1 }
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"buildkite"}) + `\b([a-z0-9]{40})\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{"buildkite"}
-}
-
-// FromData will find and optionally verify Buildkite secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Buildkite,
- Raw: []byte(resMatch),
- ExtraData: make(map[string]string),
- }
-
- if verify {
- extraData, isVerified, verificationErr := VerifyBuildKite(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- s1.ExtraData = extraData
-
- if isVerified {
- s1.AnalysisInfo = map[string]string{
- "key": resMatch,
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Buildkite
-}
-
-func (s Scanner) Description() string {
- return "Buildkite is a platform for running fast, secure, and scalable continuous integration pipelines. Buildkite API tokens can be used to access and modify pipeline data and configurations."
-}
-
-func VerifyBuildKite(ctx context.Context, client *http.Client, secret string) (map[string]string, bool, error) {
- // create a request
- // api doc: https://buildkite.com/docs/apis/rest-api/access-token#get-the-current-token
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.buildkite.com/v2/access-token", nil)
- if err != nil {
- return nil, false, err
- }
-
- // add authorization header
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", secret))
-
- res, err := client.Do(req)
- if err != nil {
- return nil, false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- var response APIResponse
-
- if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
- return nil, false, err
- }
-
- extraData := make(map[string]string)
-
- extraData["scopes"] = strings.Join(response.Scopes, ", ")
- return extraData, true, nil
- case http.StatusUnauthorized:
- return nil, false, nil
- default:
- return nil, false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
diff --git a/pkg/detectors/buildkite/v1/buildkite_integration_test.go b/pkg/detectors/buildkite/v1/buildkite_integration_test.go
deleted file mode 100644
index 8ef4ad89d1dd..000000000000
--- a/pkg/detectors/buildkite/v1/buildkite_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package buildkite
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBuildkite_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BUILDKITE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("BUILDKITE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a buildkite secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Buildkite,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a buildkite secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Buildkite,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Buildkite.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].ExtraData = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Buildkite.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/buildkite/v1/buildkite_test.go b/pkg/detectors/buildkite/v1/buildkite_test.go
deleted file mode 100644
index e4aa9d298040..000000000000
--- a/pkg/detectors/buildkite/v1/buildkite_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package buildkite
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBuildKite_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- buildkite_secret := "kimu4axq3jxxdj8un0kpo3ua2ucr05zmhh4de0r6"
- req.Header.Set("Authorization", "Bearer " + buildkite_secret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"kimu4axq3jxxdj8un0kpo3ua2ucr05zmhh4de0r6"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {buildkite}
- {buildkite AQAAABAAA ssoj8umx032r2f6sintvtw582bwvxymxgifu6gmk}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"ssoj8umx032r2f6sintvtw582bwvxymxgifu6gmk"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- buildkite_secret := "%c^wz9450iu-buildkite_secret-jonx-eab8"
- req.Header.Set("Authorization", "Bearer " + buildkite_secret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/buildkite/v2/buildkite.go b/pkg/detectors/buildkite/v2/buildkite.go
deleted file mode 100644
index b25bf744105f..000000000000
--- a/pkg/detectors/buildkite/v2/buildkite.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package buildkitev2
-
-import (
- "context"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/buildkite/v1"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-func (s Scanner) Version() int { return 2 }
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(bkua_[a-z0-9]{40})\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{"bkua_"}
-}
-
-// FromData will find and optionally verify Buildkite secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Buildkite,
- Raw: []byte(resMatch),
- }
-
- if verify {
- extraData, isVerified, verificationErr := v1.VerifyBuildKite(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- s1.ExtraData = extraData
-
- if isVerified {
- s1.AnalysisInfo = map[string]string{
- "key": resMatch,
- }
- }
-
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Buildkite
-}
-
-func (s Scanner) Description() string {
- return "Buildkite is a platform for running fast, secure, and scalable continuous integration and delivery pipelines. Buildkite access tokens can be used to interact with the Buildkite API."
-}
diff --git a/pkg/detectors/buildkite/v2/buildkite_test.go b/pkg/detectors/buildkite/v2/buildkite_test.go
deleted file mode 100644
index c7eeb19f6ca2..000000000000
--- a/pkg/detectors/buildkite/v2/buildkite_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package buildkitev2
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBuildKiteV2_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- buildkite_secret := "bkua_hqlh73m51jtho0jh12wcf2758c8fcdbv05z023ly"
- req.Header.Set("Authorization", "Bearer " + buildkite_secret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"bkua_hqlh73m51jtho0jh12wcf2758c8fcdbv05z023ly"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {}
- {AQAAABAAA bkua_j8cqyoaodi7z1fzo8u5albtyw4x9gh83yx1m6ien}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"bkua_j8cqyoaodi7z1fzo8u5albtyw4x9gh83yx1m6ien"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- buildkite_secret := "bkua_hqlh73m51jtho0jh12wcf27v05z023ly-jonx-eab8"
- req.Header.Set("Authorization", "Bearer " + buildkite_secret)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/buildkite/v2/buildkitev2_integration_test.go b/pkg/detectors/buildkite/v2/buildkitev2_integration_test.go
deleted file mode 100644
index 83c63391a080..000000000000
--- a/pkg/detectors/buildkite/v2/buildkitev2_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package buildkitev2
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBuildkite_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BUILDKITEV2_TOKEN")
- inactiveSecret := testSecrets.MustGetField("BUILDKITEV2_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a buildkite secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Buildkite,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a buildkite secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Buildkite,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Buildkite.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].ExtraData = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Buildkite.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bulbul/bulbul.go b/pkg/detectors/bulbul/bulbul.go
deleted file mode 100644
index e744b7509b97..000000000000
--- a/pkg/detectors/bulbul/bulbul.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package bulbul
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bulbul"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"bulbul"}
-}
-
-// FromData will find and optionally verify Bulbul secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Bulbul,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyBulbul(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Bulbul
-}
-
-func (s Scanner) Description() string {
- return "Bulbul is an API service. Bulbul API keys can be used to access and modify data within the service."
-}
-
-// docs: https://docs.jungleworks.com/bulbul/bulbul-api-details
-func verifyBulbul(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://prod-api.bulbul.io/view_all_users?api_key=%s", key), nil)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, err
- }
-
- bodyString := string(bodyBytes)
-
- if strings.Contains(bodyString, `"message":"Successful",`) {
- return true, nil
- } else {
- return false, nil
- }
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/bulbul/bulbul_integration_test.go b/pkg/detectors/bulbul/bulbul_integration_test.go
deleted file mode 100644
index e51371cf554e..000000000000
--- a/pkg/detectors/bulbul/bulbul_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bulbul
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBulbul_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BULBUL")
- inactiveSecret := testSecrets.MustGetField("BULBUL_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bulbul secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bulbul,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bulbul secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bulbul,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Bulbul.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Bulbul.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bulbul/bulbul_test.go b/pkg/detectors/bulbul/bulbul_test.go
deleted file mode 100644
index e9f4ced115f8..000000000000
--- a/pkg/detectors/bulbul/bulbul_test.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package bulbul
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBulBul_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.bulbul.com/v1/users?key=3kx19qpx748ldb75lsjicbs6ipit6ssm"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"3kx19qpx748ldb75lsjicbs6ipit6ssm"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bulbul}
- {bulbul AQAAABAAA r9gk8o0ctd4xq4r66d3reahu9ku4i4ht}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"r9gk8o0ctd4xq4r66d3reahu9ku4i4ht"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.bulbul.com/v1/users?key=%c^wz9450iu-3kx19qcbs6ipit6ssm-jonx-eab8"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/bulksms/bulksms.go b/pkg/detectors/bulksms/bulksms.go
deleted file mode 100644
index 8cdbe7b8dbc0..000000000000
--- a/pkg/detectors/bulksms/bulksms.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package bulksms
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bulksms"}) + `\b([a-zA-Z0-9!@#$%^&*()]{29})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bulksms"}) + `\b([A-F0-9-]{37})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-func (s Scanner) Keywords() []string {
- return []string{"bulksms"}
-}
-
-// FromData will find and optionally verify Bulksms secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueIds = make(map[string]struct{})
- var uniqueKeys = make(map[string]struct{})
-
- for _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueIds[match[1]] = struct{}{}
- }
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[match[1]] = struct{}{}
- }
-
- for id := range uniqueIds {
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Bulksms,
- Raw: []byte(key),
- RawV2: []byte(key + id),
- }
-
- if verify {
- isVerified, verificationErr := verifyBulksms(ctx, client, id, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Bulksms
-}
-
-func (s Scanner) Description() string {
- return "BulkSMS is a service used for sending SMS messages in bulk. BulkSMS credentials can be used to access and send messages through the BulkSMS API."
-}
-
-// docs: https://www.bulksms.com/developer/json/v1/
-func verifyBulksms(ctx context.Context, client *http.Client, id, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bulksms.com/v1/messages", nil)
- if err != nil {
- return false, err
- }
-
- req.SetBasicAuth(id, key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/bulksms/bulksms_integration_test.go b/pkg/detectors/bulksms/bulksms_integration_test.go
deleted file mode 100644
index aed029be3775..000000000000
--- a/pkg/detectors/bulksms/bulksms_integration_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package bulksms
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestBulksms_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BULKSMS")
- inactiveSecret := testSecrets.MustGetField("BULKSMS_INACTIVE")
- token := testSecrets.MustGetField("BULKSMS_TOKEN")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bulksms secret %s within bulksms %s", secret, token)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bulksms,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a bulksms secret %s within bulksms but %s not valid", inactiveSecret, token)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Bulksms,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Bulksms.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Bulksms.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/bulksms/bulksms_test.go b/pkg/detectors/bulksms/bulksms_test.go
deleted file mode 100644
index 792b8cfcc077..000000000000
--- a/pkg/detectors/bulksms/bulksms_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package bulksms
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestBulkSMS_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bulksmsKey := "(QGxPqRyzvt%xEKcVePJGn)k0d9a"
- bulksmsID := "381A26C47380B85F2DB572314-ACBDC267B-8"
-
- req.SetBasicAuth(bulksmsKey, bulksmsID)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"QGxPqRyzvt%xEKcVePJGn)k0d9a381A26C47380B85F2DB572314-ACBDC267B-8"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {bulksms fXHnHK&cN8H!1r5ersTDIe6ZJ8j51}
- {bulksms AQAAABAAA 04A1ED4D90D3E17-3968-66B6A571D--2134E}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"fXHnHK&cN8H!1r5ersTDIe6ZJ8j5104A1ED4D90D3E17-3968-66B6A571D--2134E"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.example.com/v1/resource"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- bulksmsKey := "(QGxPqRyzvt%xEKcVePJGn)k0d9a"
- bulksmsID := "%c^wz9450iu-iewm-jonx-eab8-/F2DB572314-ACBDC267B-8"
-
- req.SetBasicAuth(bulksmsKey, bulksmsID)
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/buttercms/buttercms.go b/pkg/detectors/buttercms/buttercms.go
deleted file mode 100644
index 36c7a12a8b00..000000000000
--- a/pkg/detectors/buttercms/buttercms.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package buttercms
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"buttercms"}) + `\b([a-z0-9]{40})\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{"buttercms"}
-}
-
-// FromData will find and optionally verify ButterCMS secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ButterCMS,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyButterCMS(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ButterCMS
-}
-
-func (s Scanner) Description() string {
- return "ButterCMS is a headless CMS that enables developers to build websites and applications with a content management system. The API keys can be used to access and modify content stored in ButterCMS."
-}
-
-// docs: https://buttercms.com/docs/api/#introduction
-func verifyButterCMS(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.buttercms.com/v2/posts/?auth_token="+key, nil)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/buttercms/buttercms_integration_test.go b/pkg/detectors/buttercms/buttercms_integration_test.go
deleted file mode 100644
index e4e5d13a7799..000000000000
--- a/pkg/detectors/buttercms/buttercms_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package buttercms
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestButterCMS_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("BUTTERCMS_TOKEN")
- inactiveSecret := testSecrets.MustGetField("BUTTERCMS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a buttercms secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ButterCMS,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a buttercms secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ButterCMS,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ButterCMS.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ButterCMS.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/buttercms/buttercms_test.go b/pkg/detectors/buttercms/buttercms_test.go
deleted file mode 100644
index 9fbd036921ac..000000000000
--- a/pkg/detectors/buttercms/buttercms_test.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package buttercms
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestButterCMS_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- func main() {
- url := "https://api.buttercms.com/v2/posts?auth_token=l7psk7wkedkpiyp4jrx5fjdnno8c89243of6yde8"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: []string{"l7psk7wkedkpiyp4jrx5fjdnno8c89243of6yde8"},
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- api-key
- {buttercms AQAAABAAA xjndr06i2jiaoqs2plf8x0cgfz976blm1dctjqv9}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"xjndr06i2jiaoqs2plf8x0cgfz976blm1dctjqv9"},
- },
- {
- name: "invalid pattern",
- input: `
- func main() {
- url := "https://api.buttercms.com/v2/posts?auth_token=l7psk7wkedkpiyp4j(rx5fjdnn)"
-
- // Create a new request with the secret as a header
- req, err := http.NewRequest("GET", url, http.NoBody)
- if err != nil {
- fmt.Println("Error creating request:", err)
- return
- }
-
- // Perform the request
- client := &http.Client{}
- resp, _ := client.Do(req)
- defer resp.Body.Close()
- }
- `,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("test %q failed: expected keywords %v to be found in the input", test.name, d.Keywords())
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- require.NoError(t, err)
-
- if len(results) != len(test.want) {
- t.Errorf("mismatch in result count: expected %d, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
-
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/caflou/caflou.go b/pkg/detectors/caflou/caflou.go
deleted file mode 100644
index f0b8905c6cac..000000000000
--- a/pkg/detectors/caflou/caflou.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package caflou
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClientTimeOut(time.Second * 10)
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(eyJhbGciOiJIUzI1NiJ9[a-zA-Z0-9._-]{135})\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{"caflou"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Caflou
-}
-
-func (s Scanner) Description() string {
- return "Caflou is a business management software used for managing projects, tasks, and finances. Caflou API keys can be used to access and modify this data."
-}
-
-// FromData will find and optionally verify Caflou secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Caflou,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyCaflou(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyCaflou(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.caflou.com/api/v1/accounts", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/caflou/caflou_integration_test.go b/pkg/detectors/caflou/caflou_integration_test.go
deleted file mode 100644
index b8eb1bec3024..000000000000
--- a/pkg/detectors/caflou/caflou_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package caflou
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCaflou_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CAFLOU")
- inactiveSecret := testSecrets.MustGetField("CAFLOU_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a caflou secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Caflou,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a caflou secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Caflou,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Caflou.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Caflou.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/caflou/caflou_test.go b/pkg/detectors/caflou/caflou_test.go
deleted file mode 100644
index aa5da01334f5..000000000000
--- a/pkg/detectors/caflou/caflou_test.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package caflou
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestCaflou_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- base_url: "https://api.example.com/instances"
- api_key: $API_KEY
- caflou_auth_token: "Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX9lkIjo1OTQ5MCwianCpIjoiOTQwZjBlODkxNPhhZjM4OTQ1OGQwMDIxIiziZXhwIjoxGzU1MTk4MDAwfQ.EMNGCPX7aNIvriX360oLFAgMwHeXxKD7N4kdcJtPqTI"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `,
- want: []string{"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX9lkIjo1OTQ5MCwianCpIjoiOTQwZjBlODkxNPhhZjM4OTQ1OGQwMDIxIiziZXhwIjoxGzU1MTk4MDAwfQ.EMNGCPX7aNIvriX360oLFAgMwHeXxKD7N4kdcJtPqTI"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/calendarific/calendarific.go b/pkg/detectors/calendarific/calendarific.go
deleted file mode 100644
index ff4cd385a33d..000000000000
--- a/pkg/detectors/calendarific/calendarific.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package calendarific
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"calendarific"}) + `\b([a-zA-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"calendarific"}
-}
-
-// FromData will find and optionally verify Calendarific secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Calendarific,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://calendarific.com/api/v2/holidays?&api_key="+resMatch+"&country=US&year=2019", nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Calendarific
-}
-
-func (s Scanner) Description() string {
- return "Calendarific provides a public API for obtaining holiday information. The API key can be used to access holiday data for various countries and years."
-}
diff --git a/pkg/detectors/calendarific/calendarific_integration_test.go b/pkg/detectors/calendarific/calendarific_integration_test.go
deleted file mode 100644
index 9b6fadba0109..000000000000
--- a/pkg/detectors/calendarific/calendarific_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package calendarific
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCalendarific_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CALENDARIFIC_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CALENDARIFIC_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a calendarific secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Calendarific,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a calendarific secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Calendarific,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Calendarific.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Calendarific.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/calendarific/calendarific_test.go b/pkg/detectors/calendarific/calendarific_test.go
deleted file mode 100644
index 37f77a241a9a..000000000000
--- a/pkg/detectors/calendarific/calendarific_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package calendarific
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- calendarific_api_key: "M9qT0uymrS2FgJ78p9CpLIlFOBLUtmao"
- base_url: "https://api.calendarific.com/v1/holidays?api_key=$calendarific_api_key"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "M9qT0uymrS2FgJ78p9CpLIlFOBLUtmao"
-)
-
-func TestCalendarific_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/calendlyapikey/calendlyapikey.go b/pkg/detectors/calendlyapikey/calendlyapikey.go
deleted file mode 100644
index 17ec832e1979..000000000000
--- a/pkg/detectors/calendlyapikey/calendlyapikey.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package calendlyapikey
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"calendly"}) + `\b(eyJ[A-Za-z0-9-_]{100,300}\.eyJ[A-Za-z0-9-_]{100,300}\.[A-Za-z0-9-_]+)\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{"calendly"}
-}
-
-// FromData will find and optionally verify CalendlyApiKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CalendlyApiKey,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.calendly.com/users/me", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CalendlyApiKey
-}
-
-func (s Scanner) Description() string {
- return "Calendly is an online scheduling tool that allows users to schedule meetings and appointments. Calendly API keys can be used to access and manage Calendly accounts and data."
-}
diff --git a/pkg/detectors/calendlyapikey/calendlyapikey_integration_test.go b/pkg/detectors/calendlyapikey/calendlyapikey_integration_test.go
deleted file mode 100644
index 4f1e88a3a387..000000000000
--- a/pkg/detectors/calendlyapikey/calendlyapikey_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package calendlyapikey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCalendlyApiKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CALENDLYAPIKEY_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CALENDLYAPIKEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a calendlyapikey secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CalendlyApiKey,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a calendlyapikey secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CalendlyApiKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CalendlyApiKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CalendlyApiKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/calendlyapikey/calendlyapikey_test.go b/pkg/detectors/calendlyapikey/calendlyapikey_test.go
deleted file mode 100644
index 943632729ec1..000000000000
--- a/pkg/detectors/calendlyapikey/calendlyapikey_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package calendlyapikey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- api_key: $API_KEY
- base_url: "https://api.example.com/v1/user"
- calendly_auth_token: "Bearer eyJuL_8UF5AiQVfO2xlr_HaoSluHz9u-Q-s1qWDWvycQhR11J9wZTmYfFpKnawuIbKjA4i340DSpYI3d3E-oEZZdcHW4cLd_OASWu-H.eyJuOUikPwjw1RKXYXfcjjeqQwdWzA4uooei_ADIUX3of4UzwTjSaaEzLWMGopW4n9Fma0nINBD1qUp_OtbhuH6dmHyv94IeX-hUYla.A0rTdrx3sJ"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "eyJuL_8UF5AiQVfO2xlr_HaoSluHz9u-Q-s1qWDWvycQhR11J9wZTmYfFpKnawuIbKjA4i340DSpYI3d3E-oEZZdcHW4cLd_OASWu-H.eyJuOUikPwjw1RKXYXfcjjeqQwdWzA4uooei_ADIUX3of4UzwTjSaaEzLWMGopW4n9Fma0nINBD1qUp_OtbhuH6dmHyv94IeX-hUYla.A0rTdrx3sJ"
-)
-
-func TestCalendlyAPIKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/calorieninja/calorieninja.go b/pkg/detectors/calorieninja/calorieninja.go
deleted file mode 100644
index 0ca01216d2f0..000000000000
--- a/pkg/detectors/calorieninja/calorieninja.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package calorieninja
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"calorieninja"}) + `\b([0-9A-Za-z]{40})\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{"calorieninja"}
-}
-
-// FromData will find and optionally verify Calorieninja secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CalorieNinja,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.calorieninjas.com/v1/nutrition?query", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-Api-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CalorieNinja
-}
-
-func (s Scanner) Description() string {
- return "CalorieNinja is a service that provides nutritional information for various foods. CalorieNinja API keys can be used to access this nutritional data."
-}
diff --git a/pkg/detectors/calorieninja/calorieninja_integration_test.go b/pkg/detectors/calorieninja/calorieninja_integration_test.go
deleted file mode 100644
index 4b680cb75011..000000000000
--- a/pkg/detectors/calorieninja/calorieninja_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package calorieninja
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCalorieninja_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CALORIENINJA")
- inactiveSecret := testSecrets.MustGetField("CALORIENINJA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a calorieninja secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CalorieNinja,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a calorieninja secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CalorieNinja,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Calorieninja.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Calorieninja.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/calorieninja/calorieninja_test.go b/pkg/detectors/calorieninja/calorieninja_test.go
deleted file mode 100644
index 145c9a069444..000000000000
--- a/pkg/detectors/calorieninja/calorieninja_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package calorieninja
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- calorieninja_api_key: "ix1aaifujilTcGEjB67e1EBBRXcr7r9cdChAR5hb"
- base_url: "https://api.example.com/v1/user"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "ix1aaifujilTcGEjB67e1EBBRXcr7r9cdChAR5hb"
-)
-
-func TestCalorieNinja_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/campayn/campayn.go b/pkg/detectors/campayn/campayn.go
deleted file mode 100644
index 018e715b0eb1..000000000000
--- a/pkg/detectors/campayn/campayn.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package campayn
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"campayn"}) + `\b([a-z0-9]{64})\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{"campayn"}
-}
-
-// FromData will find and optionally verify Campayn secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Campayn,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://campayn.com/api/v1/lists", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", "TRUEREST apikey="+resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Campayn
-}
-
-func (s Scanner) Description() string {
- return "Campayn is an email marketing service that allows users to create, send, and track email campaigns. Campayn API keys can be used to manage email lists, send emails, and track campaign performance."
-}
diff --git a/pkg/detectors/campayn/campayn_integration_test.go b/pkg/detectors/campayn/campayn_integration_test.go
deleted file mode 100644
index 7cd07d212c00..000000000000
--- a/pkg/detectors/campayn/campayn_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package campayn
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCampayn_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CAMPAYN_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CAMPAYN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a campayn secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Campayn,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a campayn secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Campayn,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Campayn.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Campayn.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/campayn/campayn_test.go b/pkg/detectors/campayn/campayn_test.go
deleted file mode 100644
index c5da83accef8..000000000000
--- a/pkg/detectors/campayn/campayn_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package campayn
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- campayn_api_key: "z6q8z47eu46wri18ygu6pc68vsizprt83jrlu5ustliyhktzoxbzhf1ycdaka978"
- base_url: "https://api.example.com/v1/user"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "z6q8z47eu46wri18ygu6pc68vsizprt83jrlu5ustliyhktzoxbzhf1ycdaka978"
-)
-
-func TestCampayn_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cannyio/cannyio.go b/pkg/detectors/cannyio/cannyio.go
deleted file mode 100644
index c85d2edde1ee..000000000000
--- a/pkg/detectors/cannyio/cannyio.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package cannyio
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"canny"}) + `\b([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\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{"canny"}
-}
-
-// FromData will find and optionally verify CannyIo secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CannyIo,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader("apiKey=" + resMatch)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://canny.io/api/v1/boards/list", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CannyIo
-}
-
-func (s Scanner) Description() string {
- return "Canny is a user feedback tool that helps you track and prioritize feature requests. Canny API keys can be used to access and manage feedback boards and other related data."
-}
diff --git a/pkg/detectors/cannyio/cannyio_integration_test.go b/pkg/detectors/cannyio/cannyio_integration_test.go
deleted file mode 100644
index 7226dcaf8355..000000000000
--- a/pkg/detectors/cannyio/cannyio_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cannyio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCannyIo_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CANNYIO_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CANNYIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cannyio secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CannyIo,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cannyio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CannyIo,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CannyIo.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CannyIo.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cannyio/cannyio_test.go b/pkg/detectors/cannyio/cannyio_test.go
deleted file mode 100644
index d4dba11b62b6..000000000000
--- a/pkg/detectors/cannyio/cannyio_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package cannyio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- cannyio_api_key: "faiiahli-goke-db0r-oxli-s20dgab9a0iv"
- base_url: "https://api.example.com/v1/user"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "faiiahli-goke-db0r-oxli-s20dgab9a0iv"
-)
-
-func TestCannyio_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/capsulecrm/capsulecrm.go b/pkg/detectors/capsulecrm/capsulecrm.go
deleted file mode 100644
index 3af7c28d044f..000000000000
--- a/pkg/detectors/capsulecrm/capsulecrm.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package capsulecrm
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"capsulecrm"}) + `\b([a-zA-Z0-9-._+=]{64})\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{"capsulecrm"}
-}
-
-// FromData will find and optionally verify CapsuleCRM secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CapsuleCRM,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.capsulecrm.com/api/v2/users", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CapsuleCRM
-}
-
-func (s Scanner) Description() string {
- return "CapsuleCRM is a customer relationship management (CRM) platform. CapsuleCRM API keys can be used to access and manage customer data and interactions."
-}
diff --git a/pkg/detectors/capsulecrm/capsulecrm_integration_test.go b/pkg/detectors/capsulecrm/capsulecrm_integration_test.go
deleted file mode 100644
index c63a12412703..000000000000
--- a/pkg/detectors/capsulecrm/capsulecrm_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package capsulecrm
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCapsuleCRM_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CAPSULECRM")
- inactiveSecret := testSecrets.MustGetField("CAPSULECRM_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a capsulecrm secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CapsuleCRM,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a capsulecrm secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CapsuleCRM,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CapsuleCRM.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CapsuleCRM.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/capsulecrm/capsulecrm_test.go b/pkg/detectors/capsulecrm/capsulecrm_test.go
deleted file mode 100644
index 18f502220c79..000000000000
--- a/pkg/detectors/capsulecrm/capsulecrm_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package capsulecrm
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- api_key: ""
- base_url: "https://api.example.com/v1/user"
- capsulecrm_auth_token: "Bearer ULeIqU-4ss+YImZYsyjPLSsm.9H.SZJ1v.KxT1D-zbaW6sg5b0R5g=koBH4X62hC"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "ULeIqU-4ss+YImZYsyjPLSsm.9H.SZJ1v.KxT1D-zbaW6sg5b0R5g=koBH4X62hC"
-)
-
-func TestCapsulecrm_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/captaindata/v1/captaindata.go b/pkg/detectors/captaindata/v1/captaindata.go
deleted file mode 100644
index 61300e3a12ab..000000000000
--- a/pkg/detectors/captaindata/v1/captaindata.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package captaindata
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-func (s Scanner) Version() int { return 1 }
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"captaindata"}) + `\b([0-9a-f]{64})\b`)
- projIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{"captaindata"}) + `\b([0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12})\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{"captaindata"}
-}
-
-// FromData will find and optionally verify CaptainData secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- projIdMatches := projIdPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, projIdMatch := range projIdMatches {
- resProjIdMatch := strings.TrimSpace(projIdMatch[1])
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CaptainData,
- Raw: []byte(resMatch),
- RawV2: []byte(resProjIdMatch + resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.captaindata.co/v2/"+resProjIdMatch, nil)
- if err != nil {
- continue
- }
- req.Header.Add("x-api-key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CaptainData
-}
-
-func (s Scanner) Description() string {
- return "CaptainData is a service for automating data extraction and processing. The API keys can be used to access and control these automation processes."
-}
diff --git a/pkg/detectors/captaindata/v1/captaindata_integration_test.go b/pkg/detectors/captaindata/v1/captaindata_integration_test.go
deleted file mode 100644
index c77264ade059..000000000000
--- a/pkg/detectors/captaindata/v1/captaindata_integration_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package captaindata
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCaptainData_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- projId := testSecrets.MustGetField("CAPTAINDATA_PROJID")
- secret := testSecrets.MustGetField("CAPTAINDATA")
- inactiveSecret := testSecrets.MustGetField("CAPTAINDATA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a captaindata project %s with captaindata secret %s within", projId, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CaptainData,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a captaindata project %s with captaindata secret %s within but not valid", projId, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CaptainData,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CaptainData.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CaptainData.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/captaindata/v1/captaindata_test.go b/pkg/detectors/captaindata/v1/captaindata_test.go
deleted file mode 100644
index 71bc022a862c..000000000000
--- a/pkg/detectors/captaindata/v1/captaindata_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package captaindata
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestCaptainData_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: "captaindata_project = '12345678-1234-1234-1234-123456789012' captaindata_api_key = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'",
- want: []string{"12345678-1234-1234-1234-1234567890121234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"},
- },
- {
- name: "finds all matches",
- input: `captaindata_project1 = '12345678-1234-1234-1234-123456789012' captaindata_api_key1 = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
-captaindata_project2 = '87654321-4321-4321-4321-210987654321' captaindata_api_key2 = 'fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321'`,
- want: []string{
- "12345678-1234-1234-1234-1234567890121234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
- "12345678-1234-1234-1234-123456789012fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321",
- "87654321-4321-4321-4321-210987654321fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321",
- "87654321-4321-4321-4321-2109876543211234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
- },
- },
- {
- name: "invalid pattern",
- input: "captaindata_project = '123456' captaindata_api_key = '1234567890'",
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/captaindata/v2/captaindata.go b/pkg/detectors/captaindata/v2/captaindata.go
deleted file mode 100644
index e24fbe37ca1a..000000000000
--- a/pkg/detectors/captaindata/v2/captaindata.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package captaindata
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-func (Scanner) Version() int { return 2 }
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"captaindata"}) + `\b([0-9a-f]{64})\b`)
- projIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{"captaindata"}) + `\b([0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12})\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{"captaindata"}
-}
-
-// FromData will find and optionally verify CaptainData secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- uniqueProjIdMatches := make(map[string]struct{})
- for _, match := range projIdPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueProjIdMatches[match[1]] = struct{}{}
- }
-
- for projId := range uniqueProjIdMatches {
- for apiKey := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CaptainData,
- Raw: []byte(apiKey),
- RawV2: []byte(projId + apiKey),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, projId, apiKey)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, apiKey)
- }
-
- results = append(results, s1)
- }
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, projId, apiKey string) (bool, map[string]string, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.captaindata.co/v3/workspace", nil)
- if err != nil {
- return false, nil, err
- }
- req.Header.Set("Authorization", "x-api-key "+apiKey)
- req.Header.Set("x-project-id", projId)
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil, nil
- case http.StatusUnauthorized:
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CaptainData
-}
-
-func (s Scanner) Description() string {
- return "CaptainData is a service for automating data extraction and processing. The API keys can be used to access and control these automation processes."
-}
diff --git a/pkg/detectors/captaindata/v2/captaindata_integration_test.go b/pkg/detectors/captaindata/v2/captaindata_integration_test.go
deleted file mode 100644
index b6efcb16e337..000000000000
--- a/pkg/detectors/captaindata/v2/captaindata_integration_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package captaindata
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCaptainData_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- projId := testSecrets.MustGetField("CAPTAINDATA_PROJID")
- secret := testSecrets.MustGetField("CAPTAINDATA")
- inactiveSecret := testSecrets.MustGetField("CAPTAINDATA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a captaindata project %s with captaindata secret %s within", projId, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CaptainData,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a captaindata project %s with captaindata secret %s within but not valid", projId, inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CaptainData,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- 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("CaptainData.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "ExtraData", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("CaptainData.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/captaindata/v2/captaindata_test.go b/pkg/detectors/captaindata/v2/captaindata_test.go
deleted file mode 100644
index 71bc022a862c..000000000000
--- a/pkg/detectors/captaindata/v2/captaindata_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package captaindata
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestCaptainData_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: "captaindata_project = '12345678-1234-1234-1234-123456789012' captaindata_api_key = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'",
- want: []string{"12345678-1234-1234-1234-1234567890121234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"},
- },
- {
- name: "finds all matches",
- input: `captaindata_project1 = '12345678-1234-1234-1234-123456789012' captaindata_api_key1 = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
-captaindata_project2 = '87654321-4321-4321-4321-210987654321' captaindata_api_key2 = 'fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321'`,
- want: []string{
- "12345678-1234-1234-1234-1234567890121234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
- "12345678-1234-1234-1234-123456789012fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321",
- "87654321-4321-4321-4321-210987654321fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321",
- "87654321-4321-4321-4321-2109876543211234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
- },
- },
- {
- name: "invalid pattern",
- input: "captaindata_project = '123456' captaindata_api_key = '1234567890'",
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/carboninterface/carboninterface.go b/pkg/detectors/carboninterface/carboninterface.go
deleted file mode 100644
index 7c768cae466f..000000000000
--- a/pkg/detectors/carboninterface/carboninterface.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package carboninterface
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"carboninterface"}) + `\b([a-zA-Z0-9]{21})\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{"carboninterface"}
-}
-
-// FromData will find and optionally verify CarbonInterface secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CarbonInterface,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{"type":"flight","passengers":2,"legs":[{"departure_airport":"sfo","destination_airport":"yyz"},{"departure_airport":"yyz","destination_airport":"sfo"}]}`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://www.carboninterface.com/api/v1/estimates", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- req.Header.Add("Content-type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CarbonInterface
-}
-
-func (s Scanner) Description() string {
- return "CarbonInterface provides an API for estimating carbon emissions for various activities. The API keys can be used to access and utilize this service."
-}
diff --git a/pkg/detectors/carboninterface/carboninterface_integration_test.go b/pkg/detectors/carboninterface/carboninterface_integration_test.go
deleted file mode 100644
index e002caa1c7ca..000000000000
--- a/pkg/detectors/carboninterface/carboninterface_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package carboninterface
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCarbonInterface_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CARBONINTERFACE")
- inactiveSecret := testSecrets.MustGetField("CARBONINTERFACE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a carboninterface secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CarbonInterface,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a carboninterface secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CarbonInterface,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CarbonInterface.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CarbonInterface.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/carboninterface/carboninterface_test.go b/pkg/detectors/carboninterface/carboninterface_test.go
deleted file mode 100644
index ab1b0fe9bd22..000000000000
--- a/pkg/detectors/carboninterface/carboninterface_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package carboninterface
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- api_key: ""
- base_url: "https://api.example.com/v1/user"
- carboninterface_auth_token: "Bearer PkN3gWaSHSIjz188TjD4h"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "PkN3gWaSHSIjz188TjD4h"
-)
-
-func TestCarbonInterface_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cashboard/cashboard.go b/pkg/detectors/cashboard/cashboard.go
deleted file mode 100644
index 7b38ffc117e0..000000000000
--- a/pkg/detectors/cashboard/cashboard.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package cashboard
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cashboard"}) + `\b([0-9A-Z]{3}-[0-9A-Z]{3}-[0-9A-Z]{3}-[0-9A-Z]{3})\b`)
- userPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cashboard"}) + `\b([0-9a-z]{1,})\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{"cashboard"}
-}
-
-// FromData will find and optionally verify Cashboard secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- userMatches := userPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, userMatch := range userMatches {
- resUser := strings.TrimSpace(userMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Cashboard,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resUser),
- }
-
- if verify {
- data := fmt.Sprintf("%s:%s", resUser, resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.cashboardapp.com/account.xml", nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Cashboard
-}
-
-func (s Scanner) Description() string {
- return "Cashboard is a financial management service. Cashboard credentials can be used to access and manage financial data and accounts."
-}
diff --git a/pkg/detectors/cashboard/cashboard_integration_test.go b/pkg/detectors/cashboard/cashboard_integration_test.go
deleted file mode 100644
index 4b4fd5db01db..000000000000
--- a/pkg/detectors/cashboard/cashboard_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cashboard
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCashboard_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CASHBOARD")
- user := testSecrets.MustGetField("SCANNER_USERNAME")
- inactiveSecret := testSecrets.MustGetField("CASHBOARD_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cashboard secret %s within %s", secret, user)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cashboard,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cashboard secret %s within %s but not valid", inactiveSecret, user)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cashboard,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Cashboard.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Cashboard.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cashboard/cashboard_test.go b/pkg/detectors/cashboard/cashboard_test.go
deleted file mode 100644
index e6c20e60721e..000000000000
--- a/pkg/detectors/cashboard/cashboard_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package cashboard
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- cashboard_key: "F1A-NEI-HY4-PZK"
- cashboard_user: "ts7z"
- auth_type: Basic
- base_url: "https://api.example.com/v1/user"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "F1A-NEI-HY4-PZKts7z"
-)
-
-func TestCashBoard_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/caspio/caspio.go b/pkg/detectors/caspio/caspio.go
deleted file mode 100644
index 3c8f34a0c73f..000000000000
--- a/pkg/detectors/caspio/caspio.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package caspio
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"caspio"}) + `\b([a-z0-9]{50})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"caspio"}) + `\b([a-z0-9]{50})\b`)
- domainPat = regexp.MustCompile(detectors.PrefixRegex([]string{"caspio"}) + `\b([a-z0-9]{8})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"caspio"}
-}
-
-// FromData will find and optionally verify Caspio secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
- domainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- resIdMatch := strings.TrimSpace(idMatch[1])
-
- for _, domainMatch := range domainMatches {
-
- resDomainMatch := strings.TrimSpace(domainMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Caspio,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch + resDomainMatch),
- }
-
- if verify {
- payload := strings.NewReader(fmt.Sprintf(`grant_type=client_credentials&client_id=%s&client_secret=%s`, resIdMatch, resMatch))
- req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("https://%s.caspio.com/oauth/token", resDomainMatch), payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "text/plain")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Caspio
-}
-
-func (s Scanner) Description() string {
- return "Caspio is a cloud platform for building custom database applications. Caspio credentials can be used to access and manage these applications."
-}
diff --git a/pkg/detectors/caspio/caspio_integration_test.go b/pkg/detectors/caspio/caspio_integration_test.go
deleted file mode 100644
index b285d94cf782..000000000000
--- a/pkg/detectors/caspio/caspio_integration_test.go
+++ /dev/null
@@ -1,119 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package caspio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCaspio_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CASPIO")
- id := testSecrets.MustGetField("CASPIO_ID")
- subdomain := testSecrets.MustGetField("CASPIO_SUBDOMAIN")
- inactiveSecret := testSecrets.MustGetField("CASPIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a caspio secret %s within caspio %s and caspio subdomain %s", secret, id, subdomain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Caspio,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a caspio secret %s within caspio %s and caspio subdomain %s but not valid", inactiveSecret, id, subdomain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Caspio,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Caspio.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Caspio.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/caspio/caspio_test.go b/pkg/detectors/caspio/caspio_test.go
deleted file mode 100644
index 7d56a91b8971..000000000000
--- a/pkg/detectors/caspio/caspio_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package caspio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- caspio_id: "qye6c979b55w55hz7nqrdgax3pufdsigwvhofsrxalgk01asty"
- caspio_secret: "x5vzi1o2fmnjywilv3obwx5n041f56v54lgral6znyccet8ibe"
- caspio_domain: xlo0xo2s
- auth_type: Client Credentials
- base_url: "https://$caspio_domain.caspio.com/v1/user"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secrets = []string{
- "qye6c979b55w55hz7nqrdgax3pufdsigwvhofsrxalgk01astyqye6c979b55w55hz7nqrdgax3pufdsigwvhofsrxalgk01astyxlo0xo2s",
- "qye6c979b55w55hz7nqrdgax3pufdsigwvhofsrxalgk01astyx5vzi1o2fmnjywilv3obwx5n041f56v54lgral6znyccet8ibexlo0xo2s",
- "x5vzi1o2fmnjywilv3obwx5n041f56v54lgral6znyccet8ibeqye6c979b55w55hz7nqrdgax3pufdsigwvhofsrxalgk01astyxlo0xo2s",
- "x5vzi1o2fmnjywilv3obwx5n041f56v54lgral6znyccet8ibex5vzi1o2fmnjywilv3obwx5n041f56v54lgral6znyccet8ibexlo0xo2s",
- }
-)
-
-func TestCaspio_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/censys/censys.go b/pkg/detectors/censys/censys.go
deleted file mode 100644
index 01a3b20235a6..000000000000
--- a/pkg/detectors/censys/censys.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package censys
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"censys"}) + `\b([a-zA-Z0-9]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"censys"}) + `\b([a-z0-9-]{36})\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{"censys"}
-}
-
-// FromData will find and optionally verify Censys secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- tokenPatMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
- userPatMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Censys,
- Raw: []byte(tokenPatMatch),
- RawV2: []byte(tokenPatMatch + userPatMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://search.censys.io/api/v1/account", nil)
- if err != nil {
- continue
- }
- req.SetBasicAuth(userPatMatch, tokenPatMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Censys
-}
-
-func (s Scanner) Description() string {
- return "Censys is a search engine that enables researchers to ask questions about the hosts and networks that compose the Internet. Censys API keys can be used to access and query this data."
-}
diff --git a/pkg/detectors/censys/censys_integration_test.go b/pkg/detectors/censys/censys_integration_test.go
deleted file mode 100644
index ec5148336993..000000000000
--- a/pkg/detectors/censys/censys_integration_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package censys
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCensys_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CENSYS")
- id := testSecrets.MustGetField("CENSYS_ID")
- inactiveSecret := testSecrets.MustGetField("CENSYS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a censys secret %s within censys %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Censys,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a censys secret %s within censys %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Censys,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Censys.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no raw v2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Censys.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/censys/censys_test.go b/pkg/detectors/censys/censys_test.go
deleted file mode 100644
index 697004fef6fc..000000000000
--- a/pkg/detectors/censys/censys_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package censys
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- censys_key: "roLtT01S6znCRhgoSwNNKY8O7AELn8e4"
- censys_user: "p4cuaz9fuonwmbfkc4di5uqsizp4yyttpu-q"
- auth_type: Basic
- base_url: "https://api.example.com/v1/user"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "roLtT01S6znCRhgoSwNNKY8O7AELn8e4p4cuaz9fuonwmbfkc4di5uqsizp4yyttpu-q"
-)
-
-func TestCensys_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/centralstationcrm/centralstationcrm.go b/pkg/detectors/centralstationcrm/centralstationcrm.go
deleted file mode 100644
index b4196acb2c89..000000000000
--- a/pkg/detectors/centralstationcrm/centralstationcrm.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package centralstationcrm
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"centralstation"}) + `\b([a-z0-9]{30})\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{"centralstationcrm"}
-}
-
-// FromData will find and optionally verify CentralStationCRM secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CentralStationCRM,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.centralstationcrm.net/api/users.json", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-apikey", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CentralStationCRM
-}
-
-func (s Scanner) Description() string {
- return "CentralStationCRM is a customer relationship management service. The API keys can be used to access and manage customer data."
-}
diff --git a/pkg/detectors/centralstationcrm/centralstationcrm_integration_test.go b/pkg/detectors/centralstationcrm/centralstationcrm_integration_test.go
deleted file mode 100644
index 364ed8e829a1..000000000000
--- a/pkg/detectors/centralstationcrm/centralstationcrm_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package centralstationcrm
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCentralStationCRM_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CENTRALSTATIONCRM")
- inactiveSecret := testSecrets.MustGetField("CENTRALSTATIONCRM_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a centralstationcrm secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CentralStationCRM,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a centralstationcrm secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CentralStationCRM,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CentralStationCRM.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CentralStationCRM.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/centralstationcrm/centralstationcrm_test.go b/pkg/detectors/centralstationcrm/centralstationcrm_test.go
deleted file mode 100644
index 01fe54b65c56..000000000000
--- a/pkg/detectors/centralstationcrm/centralstationcrm_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package centralstationcrm
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- centralstationcrm_api_key: "gyeyy7soy4lxx7yenw56iba4szfu1f"
- base_url: "https://api.example.com/v1/user"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "gyeyy7soy4lxx7yenw56iba4szfu1f"
-)
-
-func TestCentralStationCRM_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cexio/cexio.go b/pkg/detectors/cexio/cexio.go
deleted file mode 100644
index 55825f6fa701..000000000000
--- a/pkg/detectors/cexio/cexio.go
+++ /dev/null
@@ -1,133 +0,0 @@
-package cexio
-
-import (
- "context"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/hex"
- "encoding/json"
- "io"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cexio", "cex.io"}) + `\b([0-9A-Za-z]{24,27})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cexio", "cex.io"}) + `\b([0-9A-Za-z]{24,27})\b`)
- userIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cexio", "cex.io"}) + `\b([a-z]{2}[0-9]{9})\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{"cexio", "cex.io"}
-}
-
-// FromData will find and optionally verify CexIO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
- userIdMatches := userIdPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, userIdMatch := range userIdMatches {
- resUserIdMatch := strings.TrimSpace(userIdMatch[1])
-
- for _, keyMatch := range keyMatches {
- resKeyMatch := strings.TrimSpace(keyMatch[1])
-
- for _, secretMatch := range secretMatches {
- resSecretMatch := strings.TrimSpace(secretMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CexIO,
- Raw: []byte(resKeyMatch),
- RawV2: []byte(resUserIdMatch + resSecretMatch),
- }
-
- if verify {
-
- timestamp := strconv.FormatInt(time.Now().Unix()*1000, 10)
-
- signature := getCexIOPassphrase(resSecretMatch, resKeyMatch, timestamp, resUserIdMatch)
-
- payload := url.Values{}
- payload.Add("key", resKeyMatch)
- payload.Add("signature", signature)
- payload.Add("nonce", timestamp)
-
- req, err := http.NewRequestWithContext(ctx, "POST", "https://cex.io/api/balance/", strings.NewReader(payload.Encode()))
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
-
- body, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- bodyString := string(body)
- validResponse := strings.Contains(bodyString, `timestamp`)
-
- var responseObject Response
- if err := json.Unmarshal(body, &responseObject); err != nil {
- continue
- }
-
- if res.StatusCode >= 200 && res.StatusCode < 300 && validResponse {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- }
-
- return results, nil
-}
-
-type Response struct {
- Error string `json:"error"`
-}
-
-func getCexIOPassphrase(apiSecret string, apiKey string, nonce string, userId string) string {
-
- msg := nonce + userId + apiKey
- mac := hmac.New(sha256.New, []byte(apiSecret))
- mac.Write([]byte(msg))
- macsum := mac.Sum(nil)
- return strings.ToUpper(hex.EncodeToString(macsum))
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CexIO
-}
-
-func (s Scanner) Description() string {
- return "CexIO is a cryptocurrency exchange platform. CexIO API keys can be used to access and manage cryptocurrency accounts and transactions."
-}
diff --git a/pkg/detectors/cexio/cexio_integration_test.go b/pkg/detectors/cexio/cexio_integration_test.go
deleted file mode 100644
index 0fd5b1422fa4..000000000000
--- a/pkg/detectors/cexio/cexio_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cexio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCexIO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- userId := testSecrets.MustGetField("CEXIO_USERID")
- key := testSecrets.MustGetField("CEXIO_KEY")
- inactiveKey := testSecrets.MustGetField("CEXIO_KEY_INACTIVE")
- secret := testSecrets.MustGetField("CEXIO")
- inactiveSecret := testSecrets.MustGetField("CEXIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cexio userId %s with cexio key %s and cexio secret %s within", userId, key, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CexIO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cexio userId %s with cexio key %s and cexio secret %s within but not valid", userId, inactiveKey, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CexIO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CexIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CexIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/cexio/cexio_test.go b/pkg/detectors/cexio/cexio_test.go
deleted file mode 100644
index 36e8ced85659..000000000000
--- a/pkg/detectors/cexio/cexio_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package cexio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- cexio_key: "bVI24QF2B8omVr9ddfxSHtkb18D"
- cexio_secret: "2m2pEr2OLi48y2NCpSbPVwJqb"
- cex.io_userID: "zd930167221"
- auth_type: Signature
- base_url: "https://api.example.com/v1/user"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secrets = []string{
- "zd9301672212m2pEr2OLi48y2NCpSbPVwJqb",
- "zd9301672212m2pEr2OLi48y2NCpSbPVwJqb",
- "zd930167221bVI24QF2B8omVr9ddfxSHtkb18D",
- "zd930167221bVI24QF2B8omVr9ddfxSHtkb18D",
- }
-)
-
-func TestCexio_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/chartmogul/chartmogul.go b/pkg/detectors/chartmogul/chartmogul.go
deleted file mode 100644
index 34ad579d2749..000000000000
--- a/pkg/detectors/chartmogul/chartmogul.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package chartmogul
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"chartmogul"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"chartmogul"}
-}
-
-// FromData will find and optionally verify Chartmogul secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Chartmogul,
- Raw: []byte(resMatch),
- }
-
- if verify {
- data := fmt.Sprintf("%s:", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.chartmogul.com/v1/ping", nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Chartmogul
-}
-
-func (s Scanner) Description() string {
- return "ChartMogul is a subscription analytics platform that helps businesses measure, understand, and grow their subscription revenue. ChartMogul API keys can be used to access and manage subscription data."
-}
diff --git a/pkg/detectors/chartmogul/chartmogul_integration_test.go b/pkg/detectors/chartmogul/chartmogul_integration_test.go
deleted file mode 100644
index 31d43fb4798e..000000000000
--- a/pkg/detectors/chartmogul/chartmogul_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package chartmogul
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestChartmogul_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CHARTMOGUL")
- inactiveSecret := testSecrets.MustGetField("CHARTMOGUL_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a chartmogul secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Chartmogul,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a chartmogul secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Chartmogul,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Chartmogul.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Chartmogul.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/chartmogul/chartmogul_test.go b/pkg/detectors/chartmogul/chartmogul_test.go
deleted file mode 100644
index ac16af0b81fd..000000000000
--- a/pkg/detectors/chartmogul/chartmogul_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package chartmogul
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- chartmogul_key: "e8hwspf91879g0u267yq1bkoxquvwndk"
- auth_type: Basic
- base_url: "https://api.example.com/v1/user"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "e8hwspf91879g0u267yq1bkoxquvwndk"
-)
-
-func TestChartMogul_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/chatbot/chatbot.go b/pkg/detectors/chatbot/chatbot.go
deleted file mode 100644
index 0ec29b3012d1..000000000000
--- a/pkg/detectors/chatbot/chatbot.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package chatbot
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"chatbot"}) + `\b([a-zA-Z0-9_]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"chatbot"}
-}
-
-// FromData will find and optionally verify Chatbot secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Chatbot,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.chatbot.com/stories", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Chatbot
-}
-
-func (s Scanner) Description() string {
- return "Chatbot API keys are used to interact with the Chatbot service, allowing access to create, modify, and retrieve chatbot stories and other resources."
-}
diff --git a/pkg/detectors/chatbot/chatbot_integration_test.go b/pkg/detectors/chatbot/chatbot_integration_test.go
deleted file mode 100644
index ff62f399733c..000000000000
--- a/pkg/detectors/chatbot/chatbot_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package chatbot
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestChatbot_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CHATBOT_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CHATBOT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a chatbot secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Chatbot,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a chatbot secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Chatbot,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Chatbot.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Chatbot.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/chatbot/chatbot_test.go b/pkg/detectors/chatbot/chatbot_test.go
deleted file mode 100644
index e7f3fff44a0d..000000000000
--- a/pkg/detectors/chatbot/chatbot_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package chatbot
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: Bearer
- base_url: "https://api.example.com/v1/user"
- chatbot_auth_token: "Bearer 5RzDGrpFKkrA_90yM_BFmyxKKAQkgu0B"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "5RzDGrpFKkrA_90yM_BFmyxKKAQkgu0B"
-)
-
-func TestChatBot_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/chatfule/chatfule.go b/pkg/detectors/chatfule/chatfule.go
deleted file mode 100644
index 252b22ef9baf..000000000000
--- a/pkg/detectors/chatfule/chatfule.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package chatfule
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"chatfuel"}) + `\b([a-zA-Z0-9]{128})\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{"chatfuel"}
-}
-
-// FromData will find and optionally verify Chatfule secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Chatfule,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://dashboard.chatfuel.com/api/bots", nil)
- if err != nil {
- continue
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Chatfule
-}
-
-func (s Scanner) Description() string {
- return "Chatfuel is a platform for creating chatbots for Facebook Messenger and other platforms. Chatfuel API keys can be used to access and manage chatbot configurations and interactions."
-}
diff --git a/pkg/detectors/chatfule/chatfule_integration_test.go b/pkg/detectors/chatfule/chatfule_integration_test.go
deleted file mode 100644
index 405174ed66ab..000000000000
--- a/pkg/detectors/chatfule/chatfule_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package chatfule
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestChatfule_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CHATFULE")
- inactiveSecret := testSecrets.MustGetField("CHATFULE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a chatfuel secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Chatfule,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a chatfuel secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Chatfule,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Chatfuel.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Chatfuel.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/chatfule/chatfule_test.go b/pkg/detectors/chatfule/chatfule_test.go
deleted file mode 100644
index 0f98714ca4c9..000000000000
--- a/pkg/detectors/chatfule/chatfule_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package chatfule
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: Bearer
- base_url: "https://api.example.com/v1/user"
- chatfuel_auth_token: "Bearer 22ZEyoBNMXpT6rHmbuBlIJ8n19vo3tHzNTQDUku00WhHuBCAlkfkn8kQkXslseKEHARZthTrm8QfErQ5auXEr8teFIt6stHYi9sfJXM7IK0vEsezKFQwADCvMhX202eL"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "22ZEyoBNMXpT6rHmbuBlIJ8n19vo3tHzNTQDUku00WhHuBCAlkfkn8kQkXslseKEHARZthTrm8QfErQ5auXEr8teFIt6stHYi9sfJXM7IK0vEsezKFQwADCvMhX202eL"
-)
-
-func TestChatFule_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/checio/checio.go b/pkg/detectors/checio/checio.go
deleted file mode 100644
index 6aa684ee2808..000000000000
--- a/pkg/detectors/checio/checio.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package checio
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"checio"}) + `\b(pk_[a-z0-9]{45})\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{"checio"}
-}
-
-// FromData will find and optionally verify ChecIO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ChecIO,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.chec.io/v1/products?limit=25", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-Authorization", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ChecIO
-}
-
-func (s Scanner) Description() string {
- return "ChecIO is an eCommerce platform that provides APIs for managing products, carts, and orders. ChecIO API keys can be used to access and manage these eCommerce resources."
-}
diff --git a/pkg/detectors/checio/checio_integration_test.go b/pkg/detectors/checio/checio_integration_test.go
deleted file mode 100644
index e2fd6eafa416..000000000000
--- a/pkg/detectors/checio/checio_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package checio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestChecIO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CHECIO")
- inactiveSecret := testSecrets.MustGetField("CHECIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a checio secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ChecIO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a checio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ChecIO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ChecIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ChecIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/checio/checio_test.go b/pkg/detectors/checio/checio_test.go
deleted file mode 100644
index ae7a224bf752..000000000000
--- a/pkg/detectors/checio/checio_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package checio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: N/A
- base_url: "https://api.example.com/v1/user"
- checio_auth_token: "X-Authorization pk_k64v4e7f5vfun5efk7kscnvuwiuo9ioxbvxjq8qrdga0p"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "pk_k64v4e7f5vfun5efk7kscnvuwiuo9ioxbvxjq8qrdga0p"
-)
-
-func TestChecio_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/checklyhq/checklyhq.go b/pkg/detectors/checklyhq/checklyhq.go
deleted file mode 100644
index ce3f8d0f5f40..000000000000
--- a/pkg/detectors/checklyhq/checklyhq.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package checklyhq
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"checklyhq"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"checklyhq"}
-}
-
-// FromData will find and optionally verify ChecklyHQ secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ChecklyHQ,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.checklyhq.com/v1/checks", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ChecklyHQ
-}
-
-func (s Scanner) Description() string {
- return "ChecklyHQ is a monitoring service for API and browser checks. ChecklyHQ API keys can be used to access and manage these checks."
-}
diff --git a/pkg/detectors/checklyhq/checklyhq_integration_test.go b/pkg/detectors/checklyhq/checklyhq_integration_test.go
deleted file mode 100644
index 8985924f788e..000000000000
--- a/pkg/detectors/checklyhq/checklyhq_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package checklyhq
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestChecklyHQ_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CHECKLYHQ")
- inactiveSecret := testSecrets.MustGetField("CHECKLYHQ_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a checklyhq secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ChecklyHQ,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a checklyhq secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ChecklyHQ,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ChecklyHQ.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ChecklyHQ.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/checklyhq/checklyhq_test.go b/pkg/detectors/checklyhq/checklyhq_test.go
deleted file mode 100644
index ff1c031a0e48..000000000000
--- a/pkg/detectors/checklyhq/checklyhq_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package checklyhq
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: Bearer
- base_url: "https://api.example.com/v1/user"
- checklyhq_auth_token: "Bearer r3sd5apfe7p3eg1318qpbtxo36gwcct2"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "r3sd5apfe7p3eg1318qpbtxo36gwcct2"
-)
-
-func TestChecklyhq_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/checkout/checkout.go b/pkg/detectors/checkout/checkout.go
deleted file mode 100644
index 70993b448916..000000000000
--- a/pkg/detectors/checkout/checkout.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package checkout
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- // Tokens starting with sk_test are used for the app's sandbox environment while tokens starting with sk only are for production environment
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"checkout"}) + `\b((sk_|sk_test_)[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"checkout"}) + `\b(cus_[0-9a-zA-Z]{26})\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{"checkout"}
-}
-
-// FromData will find and optionally verify Checkout secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
- resIdMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Checkout,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- // Used the app's sandbox environment for this case since I can't create a live account.
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.sandbox.checkout.com/customers/"+resIdMatch, nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Checkout
-}
-
-func (s Scanner) Description() string {
- return "Checkout is a global payment solution provider. Checkout API keys can be used to process payments and manage customer data."
-}
diff --git a/pkg/detectors/checkout/checkout_integration_test.go b/pkg/detectors/checkout/checkout_integration_test.go
deleted file mode 100644
index 0337cfe5b788..000000000000
--- a/pkg/detectors/checkout/checkout_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package checkout
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCheckout_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CHECKOUT")
- inactiveSecret := testSecrets.MustGetField("CHECKOUT_INACTIVE")
- secretId := testSecrets.MustGetField("CHECKOUT_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a checkout secret %s with checkout id %s within", secret, secretId)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Checkout,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a checkout secret %s with checkout id %s within but not valid", inactiveSecret, secretId)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Checkout,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Checkout.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Checkout.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/checkout/checkout_test.go b/pkg/detectors/checkout/checkout_test.go
deleted file mode 100644
index 7132cb6f26cd..000000000000
--- a/pkg/detectors/checkout/checkout_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package checkout
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: API-Key
- base_url: "https://api.checkout.com/v1/customers/cus_DaZoK0ioakAfFaj6fyqSFQZatk"
- checkout_api_key: "sk_test_14a67eEd-21Fd-B18d-2B8D-275697febE7D"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "sk_test_14a67eEd-21Fd-B18d-2B8D-275697febE7Dcus_DaZoK0ioakAfFaj6fyqSFQZatk"
-)
-
-func TestCheckout_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/checkvist/checkvist.go b/pkg/detectors/checkvist/checkvist.go
deleted file mode 100644
index e4e05bad797a..000000000000
--- a/pkg/detectors/checkvist/checkvist.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package checkvist
-
-import (
- "context"
- "net/http"
- "net/url"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"checkvist"}) + `\b([0-9a-zA-Z]{14})\b`)
- emailPat = regexp.MustCompile(detectors.PrefixRegex([]string{"checkvist"}) + common.EmailPattern)
-)
-
-// 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{"checkvist"}
-}
-
-// FromData will find and optionally verify Checkvist secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- uniqueEmailMatches := make(map[string]struct{})
- for _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for emailMatch := range uniqueEmailMatches {
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Checkvist,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + emailMatch),
- }
-
- if verify {
- payload := url.Values{}
- payload.Add("username", emailMatch)
- payload.Add("remote_key", resMatch)
-
- req, err := http.NewRequestWithContext(ctx, "GET", "https://checkvist.com/auth/login.json?version=2", strings.NewReader(payload.Encode()))
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Checkvist
-}
-
-func (s Scanner) Description() string {
- return "Checkvist is an online task management tool. The credentials found can be used to access and manage tasks and data within Checkvist."
-}
diff --git a/pkg/detectors/checkvist/checkvist_integration_test.go b/pkg/detectors/checkvist/checkvist_integration_test.go
deleted file mode 100644
index 92e6583ea15d..000000000000
--- a/pkg/detectors/checkvist/checkvist_integration_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package checkvist
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCheckvist_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- user := testSecrets.MustGetField("CHECKVIST_EMAIL")
- secret := testSecrets.MustGetField("CHECKVIST")
- inactiveSecret := testSecrets.MustGetField("CHECKVIST_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a checkvist user %s with checkvist secret %s within", user, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Checkvist,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a checkvist user %s with checkvist secret %s within but not valid", user, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Checkvist,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Checkvist.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Checkvist.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/checkvist/checkvist_test.go b/pkg/detectors/checkvist/checkvist_test.go
deleted file mode 100644
index 86525059cf5e..000000000000
--- a/pkg/detectors/checkvist/checkvist_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package checkvist
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "wdvnusa87afxYn / testuser1005@example.com"
- invalidPattern = "wdvn-usa87a-fxp9ioasQQsstestUsQQ@example"
-)
-
-func TestCheckvist_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: fmt.Sprintf("checkvist: %s", validPattern),
- want: []string{"wdvnusa87afxYntestuser1005@example.com"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("checkvist keyword is not close to the real key and id = %s", validPattern),
- want: nil,
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("checkvist: %s", invalidPattern),
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 && test.want != nil {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- t.Errorf("expected %d results, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cicero/cicero.go b/pkg/detectors/cicero/cicero.go
deleted file mode 100644
index 2095087ed122..000000000000
--- a/pkg/detectors/cicero/cicero.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package cicero
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cicero"}) + `\b([0-9a-z]{40})\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{"cicero"}
-}
-
-// FromData will find and optionally verify Cicero secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Cicero,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://cicero.azavea.com/v3.1/account/credits_remaining?key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Cicero
-}
-
-func (s Scanner) Description() string {
- return "Cicero is a service provided by Azavea that offers various geospatial and civic data APIs. Cicero keys can be used to access and interact with these APIs."
-}
diff --git a/pkg/detectors/cicero/cicero_integration_test.go b/pkg/detectors/cicero/cicero_integration_test.go
deleted file mode 100644
index 7bb562b69e0e..000000000000
--- a/pkg/detectors/cicero/cicero_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cicero
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCicero_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CICERO")
- inactiveSecret := testSecrets.MustGetField("CICERO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cicero secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cicero,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cicero secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cicero,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Cicero.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Cicero.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cicero/cicero_test.go b/pkg/detectors/cicero/cicero_test.go
deleted file mode 100644
index 617c7fa3c463..000000000000
--- a/pkg/detectors/cicero/cicero_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package cicero
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- base_url: "https://api.cicero.com/v1/user?key=sbxod2yo56quitbyujhkig3mgtu6z49f4hh56va6"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "sbxod2yo56quitbyujhkig3mgtu6z49f4hh56va6"
-)
-
-func TestCicero_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/circleci/v1/circleci.go b/pkg/detectors/circleci/v1/circleci.go
deleted file mode 100644
index d98d3bf6f2ba..000000000000
--- a/pkg/detectors/circleci/v1/circleci.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package circleci
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strconv"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"circle"}) + `([a-fA-F0-9]{40})`)
-)
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
-
- return defaultClient
-}
-
-func (Scanner) Version() int { return 1 }
-
-// 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{"circle"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Circle
-}
-
-func (s Scanner) Description() string {
- return "CircleCI is a continuous integration and delivery platform used to build, test, and deploy software. CircleCI tokens can be used to interact with the CircleCI API and access various resources and functionalities."
-}
-
-// FromData will find and optionally verify Circle secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueTokens = make(map[string]struct{})
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokens[match[1]] = struct{}{}
- }
-
- for token := range uniqueTokens {
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_Circle,
- Raw: []byte(token),
- ExtraData: map[string]string{
- "Version": strconv.Itoa(s.Version()),
- },
- }
-
- if verify {
- // https://circleci.com/docs/api/#authentication
- isVerified, verificationErr := VerifyCircleCIToken(ctx, s.getClient(), token)
- result.Verified = isVerified
- result.SetVerificationError(verificationErr, token)
- }
-
- results = append(results, result)
- }
-
- return
-}
-
-func VerifyCircleCIToken(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://circleci.com/api/v2/me", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Accept", "application/json;")
- req.Header.Add("Circle-Token", token)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/circleci/v1/circleci_integration_test.go b/pkg/detectors/circleci/v1/circleci_integration_test.go
deleted file mode 100644
index 34d685903d28..000000000000
--- a/pkg/detectors/circleci/v1/circleci_integration_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package circleci
-
-import (
- "context"
- "fmt"
- "os"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCircleCI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CIRCLECI")
- secretInactive := testSecrets.MustGetField("CIRCLECI_INACTIVE")
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a circle secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Circle,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a circle secret %s within", secretInactive)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Circle,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CircleCI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if os.Getenv("FORCE_PASS_DIFF") == "true" {
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CircleCI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/circleci/v1/circleci_test.go b/pkg/detectors/circleci/v1/circleci_test.go
deleted file mode 100644
index 1730c18f56ff..000000000000
--- a/pkg/detectors/circleci/v1/circleci_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package circleci
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: API-Key
- base_url: "https://api.example.com/v1/user"
- circleci_api_key: "4a4fFEA0Cb7760ee42Bb1Dc82b1E4E5eDCacB9E7"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "4a4fFEA0Cb7760ee42Bb1Dc82b1E4E5eDCacB9E7"
-)
-
-func TestCircleCI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/circleci/v2/circleci.go b/pkg/detectors/circleci/v2/circleci.go
deleted file mode 100644
index 6cf34ef54a9f..000000000000
--- a/pkg/detectors/circleci/v2/circleci.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package circleci
-
-import (
- "context"
- "net/http"
- "strconv"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/circleci/v1"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(`(CCIPAT_[a-zA-Z0-9]{22}_[a-fA-F0-9]{40})`)
-)
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
-
- return defaultClient
-}
-
-func (Scanner) Version() int { return 2 }
-
-// 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{"CCIPAT_"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Circle
-}
-
-func (s Scanner) Description() string {
- return "CircleCI is a continuous integration and delivery platform used to build, test, and deploy software. CircleCI tokens can be used to interact with the CircleCI API and access various resources and functionalities."
-}
-
-// FromData will find and optionally verify Circle secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueTokens = make(map[string]struct{})
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokens[match[1]] = struct{}{}
- }
-
- for token := range uniqueTokens {
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_Circle,
- Raw: []byte(token),
- ExtraData: map[string]string{
- "Version": strconv.Itoa(s.Version()),
- },
- }
-
- if verify {
- isVerified, verificationErr := v1.VerifyCircleCIToken(ctx, s.getClient(), token)
- result.Verified = isVerified
- result.SetVerificationError(verificationErr, token)
- }
-
- results = append(results, result)
- }
-
- return
-}
diff --git a/pkg/detectors/circleci/v2/circleci_integration_test.go b/pkg/detectors/circleci/v2/circleci_integration_test.go
deleted file mode 100644
index e514bc23fef6..000000000000
--- a/pkg/detectors/circleci/v2/circleci_integration_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package circleci
-
-import (
- "context"
- "fmt"
- "os"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCircleCI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CIRCLECI")
- secretInactive := testSecrets.MustGetField("CIRCLECI_INACTIVE")
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a circle secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Circle,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a circle secret %s within", secretInactive)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Circle,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CircleCI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if os.Getenv("FORCE_PASS_DIFF") == "true" {
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- got[i].ExtraData = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CircleCI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/circleci/v2/circleci_test.go b/pkg/detectors/circleci/v2/circleci_test.go
deleted file mode 100644
index d67f067a4f1b..000000000000
--- a/pkg/detectors/circleci/v2/circleci_test.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package circleci
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestCircleCI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: API-Key
- base_url: "https://api.example.com/v1/user"
- api_key: "CCIPAT_FAKEd5qPreGFAKEaQxBi6i_914bf0042f4f2d34e1d2ef6615c051a5caf70172"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `,
- want: []string{"CCIPAT_FAKEd5qPreGFAKEaQxBi6i_914bf0042f4f2d34e1d2ef6615c051a5caf70172"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/clarifai/clarifai.go b/pkg/detectors/clarifai/clarifai.go
deleted file mode 100644
index 7b77897b9cdc..000000000000
--- a/pkg/detectors/clarifai/clarifai.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package clarifai
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"clarifai"}) + `\b([a-zA-Z0-9]{32})\b`) // could be an api key tied to an app or a personal access token (pat)
-)
-
-// 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{"clarifai"}
-}
-
-// FromData will find and optionally verify Clarifai secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Clarifai,
- Raw: []byte(resMatch),
- }
-
- if verify {
- // test for api key
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.clarifai.com/v2/inputs", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Key %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
-
- if !s1.Verified {
- // test for pat
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.clarifai.com/v2/users/me", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Key %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Clarifai
-}
-
-func (s Scanner) Description() string {
- return "Clarifai is an AI platform for visual recognition. Clarifai API keys can be used to access and manage visual recognition models and data."
-}
diff --git a/pkg/detectors/clarifai/clarifai_integration_test.go b/pkg/detectors/clarifai/clarifai_integration_test.go
deleted file mode 100644
index 96ab6fe5ae0e..000000000000
--- a/pkg/detectors/clarifai/clarifai_integration_test.go
+++ /dev/null
@@ -1,137 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package clarifai
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestClarifai_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- apiKey := testSecrets.MustGetField("CLARIFAI_API_KEY")
- inactiveApiKey := testSecrets.MustGetField("CLARIFAI_API_KEY_INACTIVE")
- pat := testSecrets.MustGetField("CLARIFAI_PAT")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clarifai api key %s within", apiKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Clarifai,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clarifai pat %s within", pat)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Clarifai,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clarifai secret %s within but unverified", inactiveApiKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Clarifai,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Clarifai.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Clarifai.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/clarifai/clarifai_test.go b/pkg/detectors/clarifai/clarifai_test.go
deleted file mode 100644
index 62d226964c91..000000000000
--- a/pkg/detectors/clarifai/clarifai_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package clarifai
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: API-Key
- base_url: "https://api.example.com/v1/user"
- clarifai_key: "WCFcfUCsl2P3vCJuFgDuLeUXduycoZli"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "WCFcfUCsl2P3vCJuFgDuLeUXduycoZli"
-)
-
-func TestClarifai_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/clearbit/clearbit.go b/pkg/detectors/clearbit/clearbit.go
deleted file mode 100644
index 92f5a4a0d906..000000000000
--- a/pkg/detectors/clearbit/clearbit.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package clearbit
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"clearbit"}) + `\b([0-9a-z_]{35})\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{"clearbit"}
-}
-
-// FromData will find and optionally verify Clearbit secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Clearbit,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://person.clearbit.com/v1/people/email/alex@alexmaccaw.com", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Clearbit
-}
-
-func (s Scanner) Description() string {
- return "Clearbit provides powerful APIs for enriching data about companies and people. Clearbit API keys can be used to access and retrieve detailed information about these entities."
-}
diff --git a/pkg/detectors/clearbit/clearbit_integration_test.go b/pkg/detectors/clearbit/clearbit_integration_test.go
deleted file mode 100644
index da8ba1c26924..000000000000
--- a/pkg/detectors/clearbit/clearbit_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package clearbit
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestClearbit_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLEARBIT")
- inactiveSecret := testSecrets.MustGetField("CLEARBIT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clearbit secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Clearbit,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clearbit secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Clearbit,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Clearbit.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Clearbit.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/clearbit/clearbit_test.go b/pkg/detectors/clearbit/clearbit_test.go
deleted file mode 100644
index 522d691194da..000000000000
--- a/pkg/detectors/clearbit/clearbit_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package clearbit
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: Bearer
- base_url: "https://api.example.com/v1/user"
- clearbit_auth_token: "Bearer 9itvicgfiyq3ry6g03qwhwc_0s309dy8woh"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "9itvicgfiyq3ry6g03qwhwc_0s309dy8woh"
-)
-
-func TestClearBit_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/clickhelp/clickhelp.go b/pkg/detectors/clickhelp/clickhelp.go
deleted file mode 100644
index 08b930c16b8c..000000000000
--- a/pkg/detectors/clickhelp/clickhelp.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package clickhelp
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- portalPat = regexp.MustCompile(`\b([0-9A-Za-z-]{3,20}\.(?:try\.)?clickhelp\.co)\b`)
- emailPat = regexp.MustCompile(common.EmailPattern)
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"clickhelp", "key", "token", "api", "secret"}) + `\b([0-9A-Za-z]{24})\b`)
-)
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ClickHelp
-}
-
-func (s Scanner) Description() string {
- return "ClickHelp is a documentation tool that allows users to create and manage online documentation. ClickHelp API keys can be used to access and modify documentation data."
-}
-
-// 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{"clickhelp.co"}
-}
-
-// FromData will find and optionally verify Clickhelp secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniquePortalLinks, uniqueEmails, uniqueAPIKeys = make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})
-
- for _, match := range portalPat.FindAllStringSubmatch(dataStr, -1) {
- uniquePortalLinks[match[1]] = struct{}{}
- }
-
- for _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueEmails[match[1]] = struct{}{}
- }
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueAPIKeys[match[1]] = struct{}{}
- }
-
- for portalLink := range uniquePortalLinks {
- for email := range uniqueEmails {
- for apiKey := range uniqueAPIKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ClickHelp,
- Raw: []byte(portalLink),
- RawV2: []byte(portalLink + email),
- }
-
- if verify {
- isVerified, verificationErr := verifyClickHelp(ctx, client, portalLink, email, apiKey)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- s1.SetPrimarySecretValue(apiKey) // line number will point to api key
- }
-
- results = append(results, s1)
- }
- }
- }
-
- return results, nil
-}
-
-func verifyClickHelp(ctx context.Context, client *http.Client, portalLink, email, apiKey string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://%s/api/v1/projects", portalLink), http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.SetBasicAuth(email, apiKey)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/clickhelp/clickhelp_integration_test.go b/pkg/detectors/clickhelp/clickhelp_integration_test.go
deleted file mode 100644
index 45355743bc54..000000000000
--- a/pkg/detectors/clickhelp/clickhelp_integration_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package clickhelp
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestClickhelp_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- email := testSecrets.MustGetField("SCANNERS_EMAIL")
- server := testSecrets.MustGetField("CLICKHELP_SERVER")
- key := testSecrets.MustGetField("CLICKHELP")
- inactiveKey := testSecrets.MustGetField("CLICKHELP_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clickhelp secret %s within clickhelp email %s and clickhelp server %s", key, email, server)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ClickHelp,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clickhelp secret %s within clickhelp email %s and clickhelp server %s but not valid", inactiveKey, email, server)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ClickHelp,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Clickhelp.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].RawV2 = nil
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "primarySecret")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("AdafruitIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/clickhelp/clickhelp_test.go b/pkg/detectors/clickhelp/clickhelp_test.go
deleted file mode 100644
index ff56a418f0ba..000000000000
--- a/pkg/detectors/clickhelp/clickhelp_test.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package clickhelp
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestClickHelp_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- user_email: "test-user@clickhelp.co"
- key: "XzUkp562BtmjfRGoOGBiLLNu"
- portal: testingdev.try.clickhelp.co
- auth_type: Basic
- base_url: "https://testing-dev.try.clickhelp.co/v1/user"
- auth_token: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `,
- want: []string{
- "testing-dev.try.clickhelp.cotest-user@clickhelp.co",
- "testingdev.try.clickhelp.cotest-user@clickhelp.co",
- },
- },
- {
- name: "valid pattern - xml",
- input: `
-
- GLOBAL
- {test-user01@clickhelp.co}
- {AQAAABAAA XzUkp562BtmjfRGoOGBiLLNu}
- company-prod.clickhelp.co
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"company-prod.clickhelp.cotest-user01@clickhelp.co"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/clicksendsms/clicksendsms.go b/pkg/detectors/clicksendsms/clicksendsms.go
deleted file mode 100644
index 03416b0f95ac..000000000000
--- a/pkg/detectors/clicksendsms/clicksendsms.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package clicksendsms
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(common.UUIDPatternUpperCase)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sms"}) + common.EmailPattern)
-)
-
-// 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{"clicksendsms"}
-}
-
-// FromData will find and optionally verify ClickSendsms secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, idMatch := range idMatches {
- resIdMatch := strings.TrimSpace(idMatch[0][strings.LastIndex(idMatch[0], " ")+1:])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ClickSendsms,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
-
- data := fmt.Sprintf("%s:%s", resIdMatch, resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
-
- req, err := http.NewRequestWithContext(ctx, "GET", "https://rest.clicksend.com/v3/account", nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
-
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ClickSendsms
-}
-
-func (s Scanner) Description() string {
- return "ClickSend is a global leader in business communication solutions, providing a range of services including SMS, email, and voice. ClickSend API keys can be used to access and manage these communication services."
-}
diff --git a/pkg/detectors/clicksendsms/clicksendsms_integration_test.go b/pkg/detectors/clicksendsms/clicksendsms_integration_test.go
deleted file mode 100644
index 7808808aee35..000000000000
--- a/pkg/detectors/clicksendsms/clicksendsms_integration_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package clicksendsms
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestClickSendsms_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLICKSENDSMS_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CLICKSENDSMS_INACTIVE")
- email := testSecrets.MustGetField("CLICKSENDSMS_EMAIL")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clicksendsms secret %s within clicksendsmsemail %s", secret, email)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ClickSendsms,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clicksendsms secret %s within clicksendsmsemail %s but not valid", inactiveSecret, email)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ClickSendsms,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ClickSendsms.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no raw v2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ClickSendsms.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/clicksendsms/clicksendsms_test.go b/pkg/detectors/clicksendsms/clicksendsms_test.go
deleted file mode 100644
index 4bdc3dbca23a..000000000000
--- a/pkg/detectors/clicksendsms/clicksendsms_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package clicksendsms
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File for clicksendsms: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: Basic
- base_url: "https://api.example.com/v1/sms"
- sms_id: G9TXU2YD-NOYB-LLSX-21NU-5CX5SIA330Z7
- sms_email: user-test@clicksend.sms
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "G9TXU2YD-NOYB-LLSX-21NU-5CX5SIA330Z7user-test@clicksend.sms"
-)
-
-func TestClickSendSMS_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken.go b/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken.go
deleted file mode 100644
index 1f5907bf043c..000000000000
--- a/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package clickuppersonaltoken
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"clickup"}) + `\b(pk_[0-9]{7,9}_[0-9A-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"clickup"}
-}
-
-// FromData will find and optionally verify ClickupPersonalToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for resMatch := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ClickupPersonalToken,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- isVerified, err := verifyToken(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ClickupPersonalToken
-}
-
-func (s Scanner) Description() string {
- return "ClickUp is a project management tool. Personal tokens can be used to access and modify data within ClickUp on behalf of a user."
-}
-
-func verifyToken(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.clickup.com/api/v2/user", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", token)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
diff --git a/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken_integration_test.go b/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken_integration_test.go
deleted file mode 100644
index 2be96fda4483..000000000000
--- a/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken_integration_test.go
+++ /dev/null
@@ -1,147 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package clickuppersonaltoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestClickupPersonalToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLICKUPPERSONALTOKEN")
- inactiveSecret := testSecrets.MustGetField("CLICKUPPERSONALTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clickuppersonaltoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ClickupPersonalToken,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clickuppersonaltoken secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ClickupPersonalToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found verifiable secret, verification failed due to unexpected API response",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clickuppersonaltoken secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ClickupPersonalToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ClickupPersonalToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
-
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- got[i].Raw = nil
- }
-
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("ClickupPersonalToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken_test.go b/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken_test.go
deleted file mode 100644
index 673c0921adc2..000000000000
--- a/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package clickuppersonaltoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- base_url: "https://api.example.com/v1/user"
- clickup_token: "pk_7043602_WIKY22PAKCVC1S5Q2X6119IK7N1UL8VY"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "pk_7043602_WIKY22PAKCVC1S5Q2X6119IK7N1UL8VY"
-)
-
-func TestClickupPersonalToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cliengo/cliengo.go b/pkg/detectors/cliengo/cliengo.go
deleted file mode 100644
index bd9d877e9ba6..000000000000
--- a/pkg/detectors/cliengo/cliengo.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package cliengo
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cliengo"}) + `\b([0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12})\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{"cliengo"}
-}
-
-// FromData will find and optionally verify Cliengo secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Cliengo,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.cliengo.com/1.0/account?api_key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Cliengo
-}
-
-func (s Scanner) Description() string {
- return "Cliengo is a chatbot service that helps businesses convert website visitors into leads. Cliengo API keys can be used to access and manage the chatbot configurations and data."
-}
diff --git a/pkg/detectors/cliengo/cliengo_integration_test.go b/pkg/detectors/cliengo/cliengo_integration_test.go
deleted file mode 100644
index 1d31d5cf0a4f..000000000000
--- a/pkg/detectors/cliengo/cliengo_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cliengo
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCliengo_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLIENGO")
- inactiveSecret := testSecrets.MustGetField("CLIENGO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cliengo secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cliengo,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cliengo secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cliengo,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Cliengo.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Cliengo.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/cliengo/cliengo_test.go b/pkg/detectors/cliengo/cliengo_test.go
deleted file mode 100644
index e6250b8746fe..000000000000
--- a/pkg/detectors/cliengo/cliengo_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package cliengo
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- base_url: "https://api.cliengo.com/v1/user?key=9e4635bc-28dc-25d3-8546-2b30115d9a7b"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "9e4635bc-28dc-25d3-8546-2b30115d9a7b"
-)
-
-func TestCliengo_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/clientary/clientary.go b/pkg/detectors/clientary/clientary.go
deleted file mode 100644
index 2541a4b03ef5..000000000000
--- a/pkg/detectors/clientary/clientary.go
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
-RoninApp rebranded to Clientary
-
-Article: https://www.clientary.com/articles/a-new-brand/
-*/
-package clientary
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ronin", "clientary"}) + `\b([0-9a-zA-Z]{24,26})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ronin", "clientary"}) + `\b([0-9Aa-zA-Z-]{4,25})\b`)
-
- errAccountNotFound = errors.New("account not found")
-)
-
-// 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{"ronin", "clientary"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Clientary
-}
-
-func (s Scanner) Description() string {
- return "Clientary is a one software app to manage Clients, Invoices, Projects, Proposals, Estimates, Hours, Payments, Contractors and Staff. Clientary keys can be used to access and manage invoices and other resources."
-}
-
-// FromData will find and optionally verify RoninApp secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueIDs, uniqueAPIKeys = make(map[string]struct{}), make(map[string]struct{})
-
- for _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueIDs[match[1]] = struct{}{}
- }
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueAPIKeys[match[1]] = struct{}{}
- }
-
- for apiKey := range uniqueAPIKeys {
- for id := range uniqueIDs {
- // since regex matches can overlap, continue only if both apiKey and id are the same.
- if apiKey == id {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Clientary,
- Raw: []byte(apiKey),
- RawV2: []byte(apiKey + ":" + id),
- ExtraData: make(map[string]string),
- }
-
- if verify {
- isVerified, verificationErr := verifyClientaryAPIKey(ctx, client, id, apiKey)
- s1.Verified = isVerified
- if verificationErr != nil {
- // remove the account ID if not found to prevent reuse during other API key checks.
- if errors.Is(verificationErr, errAccountNotFound) {
- delete(uniqueIDs, id)
- continue
- }
-
- s1.SetVerificationError(verificationErr, apiKey)
- }
-
- // If a verified result is found, attach rebranding documentation to inform the user about the RoninApp rebranding to Clientary.
- if s1.Verified {
- s1.ExtraData["Rebrading Docs"] = "https://www.clientary.com/articles/a-new-brand/"
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-// docs: https://www.clientary.com/api
-func verifyClientaryAPIKey(ctx context.Context, client *http.Client, id, apiKey string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+id+".clientary.com/api/v2/invoices", http.NoBody)
- if err != nil {
- return false, nil
- }
-
- req.SetBasicAuth(apiKey, apiKey)
- req.Header.Add("Accept", "application/json")
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusForbidden, http.StatusUnauthorized:
- return false, nil
- case http.StatusNotFound:
- // API return 404 if the account id does not exist
- return false, errAccountNotFound
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/clientary/clientary_integration_test.go b/pkg/detectors/clientary/clientary_integration_test.go
deleted file mode 100644
index afb7c2ef8f15..000000000000
--- a/pkg/detectors/clientary/clientary_integration_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package clientary
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestRoninApp_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("RONINAPP")
- inactiveSecret := testSecrets.MustGetField("RONINAPP_INACTIVE")
- domain := testSecrets.MustGetField("RONINAPP_DOMAIN")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clientary secret %s and clientaryDomain %s", secret, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Clientary,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Clientary,
- Verified: true,
- ExtraData: map[string]string{
- "Rebrading Docs": "https://www.clientary.com/articles/a-new-brand/",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ronin secret %s and ronaindomain %s but not valid", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Clientary,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("RoninApp.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("RoninApp.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/clientary/clientary_test.go b/pkg/detectors/clientary/clientary_test.go
deleted file mode 100644
index e5b35c7b8702..000000000000
--- a/pkg/detectors/clientary/clientary_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package clientary
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestRoninApp_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword ronin",
- input: `
- # some random code
- data := getIDFromDatabase(ctx)
- roninAPIKey := ZycQ0G6IBgNsBWytwzwVKixyz
- roninDomain := truffle-dev.roninapp.com
- `,
- want: []string{"ZycQ0G6IBgNsBWytwzwVKixyz:truffle-dev"},
- },
- {
- name: "valid pattern - with keyword clientary",
- input: `
- # some random code
- data := getIDFromDatabase(ctx)
- clientaryAPIKey := ZycQ0G6IBgNsBWytwzwVKixyz
- clientaryDomain := truffle-dev.clientary.com
- `,
- want: []string{"ZycQ0G6IBgNsBWytwzwVKixyz:truffle-dev"},
- },
- {
- name: "invalid pattern",
- input: `
- # some random code
- data := getIDFromDatabase(ctx)
- roninAPIKey := ZycQ0G6IBg-NsBWytwzwVKixyz
- rominDomain := t_de.roninapp.com
- `,
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/clinchpad/clinchpad.go b/pkg/detectors/clinchpad/clinchpad.go
deleted file mode 100644
index 9b025b533f0d..000000000000
--- a/pkg/detectors/clinchpad/clinchpad.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package clinchpad
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"clinchpad"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"clinchpad"}
-}
-
-// FromData will find and optionally verify Clinchpad secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Clinchpad,
- Raw: []byte(resMatch),
- }
-
- if verify {
- data := fmt.Sprintf("api-key:%s", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.clinchpad.com/api/v1/pipelines", nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Clinchpad
-}
-
-func (s Scanner) Description() string {
- return "Clinchpad is a CRM tool. Clinchpad API keys can be used to access and modify data within Clinchpad."
-}
diff --git a/pkg/detectors/clinchpad/clinchpad_integration_test.go b/pkg/detectors/clinchpad/clinchpad_integration_test.go
deleted file mode 100644
index 68d05a39f247..000000000000
--- a/pkg/detectors/clinchpad/clinchpad_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package clinchpad
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestClinchpad_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLINCHPAD_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CLINCHPAD_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clinchpad secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Clinchpad,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clinchpad secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Clinchpad,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Clinchpad.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Clinchpad.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/clinchpad/clinchpad_test.go b/pkg/detectors/clinchpad/clinchpad_test.go
deleted file mode 100644
index 651f1d960550..000000000000
--- a/pkg/detectors/clinchpad/clinchpad_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package clinchpad
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- base_url: "https://api.example.com/v1/user"
- clinchpad_key: "3v1xo5r03ghc538iwzbzeddwqulnun8h"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "3v1xo5r03ghc538iwzbzeddwqulnun8h"
-)
-
-func TestClinchPad_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/clockify/clockify.go b/pkg/detectors/clockify/clockify.go
deleted file mode 100644
index 524ca4571879..000000000000
--- a/pkg/detectors/clockify/clockify.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package clockify
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"clockify"}) + `\b([a-zA-Z0-9]{48})\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{"clockify"}
-}
-
-// FromData will find and optionally verify Clockify secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Clockify,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.clockify.me/api/v1/user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("content-type", "application/json")
- req.Header.Add("X-Api-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Clockify
-}
-
-func (s Scanner) Description() string {
- return "Clockify is a time tracking software. Clockify API keys can be used to access and modify time tracking data."
-}
diff --git a/pkg/detectors/clockify/clockify_integration_test.go b/pkg/detectors/clockify/clockify_integration_test.go
deleted file mode 100644
index 618512566d1b..000000000000
--- a/pkg/detectors/clockify/clockify_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package clockify
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestClockify_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLOCKIFY")
- inactiveSecret := testSecrets.MustGetField("CLOCKIFY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clockify secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Clockify,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clockify secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Clockify,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Clockify.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Clockify.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/clockify/clockify_test.go b/pkg/detectors/clockify/clockify_test.go
deleted file mode 100644
index cff7b09b2f2d..000000000000
--- a/pkg/detectors/clockify/clockify_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package clockify
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- base_url: "https://api.example.com/v1/user"
- clockify_key: "kfJkRn7Knahh6pyDOL82NqNq5c4VLUNulVe5CMyJpIK9NXQC"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "kfJkRn7Knahh6pyDOL82NqNq5c4VLUNulVe5CMyJpIK9NXQC"
-)
-
-func TestClockify_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/clockworksms/clockworksms.go b/pkg/detectors/clockworksms/clockworksms.go
deleted file mode 100644
index 18970c049f5e..000000000000
--- a/pkg/detectors/clockworksms/clockworksms.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package clockworksms
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- userKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"clockwork", "textanywhere"}) + `\b([0-9]{5})\b`)
- tokenPat = regexp.MustCompile(detectors.PrefixRegex([]string{"clockwork", "textanywhere"}) + `\b([0-9a-zA-Z]{24})\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{"clockworksms", "textanywhere"}
-}
-
-// FromData will find and optionally verify Clockworksms secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- userKeyMatches := userKeyPat.FindAllStringSubmatch(dataStr, -1)
- tokenMatches := tokenPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range userKeyMatches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, tokenMatch := range tokenMatches {
- tokenRes := strings.TrimSpace(tokenMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ClockworkSMS,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + tokenRes),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.textanywhere.com/API/v1.0/REST/status", nil)
- if err != nil {
- continue
- }
- req.Header.Add("user_key", resMatch)
- req.Header.Add("access_token", tokenRes)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ClockworkSMS
-}
-
-func (s Scanner) Description() string {
- return "Clockwork SMS is a service used for sending SMS messages. User keys and access tokens can be used to authenticate and send messages via the Clockwork SMS API."
-}
diff --git a/pkg/detectors/clockworksms/clockworksms_integration_test.go b/pkg/detectors/clockworksms/clockworksms_integration_test.go
deleted file mode 100644
index 0c1e4d500148..000000000000
--- a/pkg/detectors/clockworksms/clockworksms_integration_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package clockworksms
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestClockworksms_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- userKey := testSecrets.MustGetField("CLOCKWORKSMS_USERKEY")
- token := testSecrets.MustGetField("CLOCKWORKSMS_TOKEN")
- inactiveToken := testSecrets.MustGetField("CLOCKWORKSMS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clockworksms secret %s and clockworksms token %s within", userKey, token)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ClockworkSMS,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clockworksms secret %s and clockworksms token %s within but not valid", userKey, inactiveToken)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ClockworkSMS,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Clockworksms.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no raw v2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Clockworksms.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/clockworksms/clockworksms_test.go b/pkg/detectors/clockworksms/clockworksms_test.go
deleted file mode 100644
index 40f0c39810d5..000000000000
--- a/pkg/detectors/clockworksms/clockworksms_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package clockworksms
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- base_url: "https://api.textanywhere.com/v1/user"
- clockwork_key: "84473"
- clockwork_token: "YROh7NZbZxHwiSc9pMNIAGYs"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "84473YROh7NZbZxHwiSc9pMNIAGYs"
-)
-
-func TestClockWorkSMS_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/closecrm/close.go b/pkg/detectors/closecrm/close.go
deleted file mode 100644
index 47ecc40f6fe6..000000000000
--- a/pkg/detectors/closecrm/close.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package closecrm
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(api_[a-z0-9A-Z.]{45})\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{"close"}
-}
-
-// FromData will find and optionally verify Close secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Close,
- Raw: []byte(resMatch),
- }
-
- if verify {
- data := fmt.Sprintf("%s:", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.close.com/api/v1/me/", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc))
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Close
-}
-
-func (s Scanner) Description() string {
- return "Close is a CRM software that helps businesses manage sales and customer relationships. Close API keys can be used to access and manipulate CRM data."
-}
diff --git a/pkg/detectors/closecrm/close_integration_test.go b/pkg/detectors/closecrm/close_integration_test.go
deleted file mode 100644
index 2a4940dc432c..000000000000
--- a/pkg/detectors/closecrm/close_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package closecrm
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestClose_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLOSE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CLOSE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a close secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Close,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a close secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Close,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Close.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Close.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/closecrm/close_test.go b/pkg/detectors/closecrm/close_test.go
deleted file mode 100644
index e2a2bb59a71c..000000000000
--- a/pkg/detectors/closecrm/close_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package closecrm
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- base_url: "https://api.example.com/v1/user"
- close_key: "api_3cyEW8syFEmeND561qJ9Sj8mT6E0VyWqY7h6cjJIBtc2e"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "api_3cyEW8syFEmeND561qJ9Sj8mT6E0VyWqY7h6cjJIBtc2e"
-)
-
-func TestCloseCRM_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudconvert/cloudconvert.go b/pkg/detectors/cloudconvert/cloudconvert.go
deleted file mode 100644
index 835f3e7597ae..000000000000
--- a/pkg/detectors/cloudconvert/cloudconvert.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package cloudconvert
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.MaxSecretSizeProvider = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloudconvert"}) + common.BuildRegexJWT("30,34", "200,500", "600,700"))
-)
-
-// 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{"cloudconvert"}
-}
-
-const maxJWTSize = 1300
-
-// MaxSecretSize returns the maximum size of a secret that this detector can find.
-func (s Scanner) MaxSecretSize() int64 { return maxJWTSize }
-
-// FromData will find and optionally verify CloudConvert secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CloudConvert,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.cloudconvert.com/v2/users/me", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.cloudconvert+json; version=3")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CloudConvert
-}
-
-func (s Scanner) Description() string {
- return "CloudConvert is a file conversion service. CloudConvert API keys can be used to access and manage file conversion operations."
-}
diff --git a/pkg/detectors/cloudconvert/cloudconvert_integration_test.go b/pkg/detectors/cloudconvert/cloudconvert_integration_test.go
deleted file mode 100644
index 9109b4a589fc..000000000000
--- a/pkg/detectors/cloudconvert/cloudconvert_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cloudconvert
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCloudConvert_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLOUDCONVERT")
- inactiveSecret := testSecrets.MustGetField("CLOUDCONVERT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudconvert secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudConvert,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudconvert secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudConvert,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CloudConvert.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CloudConvert.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudconvert/cloudconvert_test.go b/pkg/detectors/cloudconvert/cloudconvert_test.go
deleted file mode 100644
index 5caac3f9eae1..000000000000
--- a/pkg/detectors/cloudconvert/cloudconvert_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package cloudconvert
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- base_url: "https://api.example.com/v1/user"
- cloudconvert_key: "eypn1gEV3BnckI3jcYzUvliSbukvxzO5acvE?ey8VEaR0lmpRa4IXv02fDPnlfdukWtb1/p-nlQlPVGnB52f9KwY4q98aVghXZqoit4AeFxMAHcCytOj61o8lHdcUF9fIcyF2HaFIk/k3Hdt7pS/5rb2eeWcEvc-5XB0T_Oh68AtCG8mOPpwKvzrhhIuEJck3vtFncgbDrSxg5mKkw924rMLP3Tb5tgIRuawZLwBxJL/qVIhAzfDGiIeNTzYOB9zHfHlfw3aZ1i/terePSN5EafVJ1yYw1KRXWL9/kPdAO0yFwSv3mUWx04oIIUURG6QKwO0rk7L0eAxnu4djnSXtqdvH_G50H1SSwwfKUg2Xz25-OZLkhxiaxEMMMY=3x0Yjhs7O1KFkI5gUQKH_VYAU2bJSAqpCKsxaYrdw91wUoya5rflCBVDHjC/BsezIkPFFmEu7sqs3WJg6dZeAiguYx7uZtDx1ILH18f29q9o34bM9SNolZNcG3fN7L2eWjilbmUq/Ty2545WkbHTjlcjLlHPAAjzLebfcFnlMSKH9Tqb/qx3G1z8wfzMa3dn3iRqNHwfmGOmfgK7RjtlZwoVruMjDWEza/o8imZF513yM7FrHTJkTFa1JjVbjU/C85ItZTiJsBUKAt/DbLg6W7lieKgHbgmz3cuwgVR7YDLZJB056TRcU9wrV0SUYDz0gogrpOEnZxdo4fb5UcCllj/AD/dYsfqVSHtTxKWBhun9Iqmx8FjgPtFCFugTxfaaHZ9dUC7TPahdSxixGvnu8EEvAs0Te85eJ9iyeq628Tvboz9J7KMq/uwflJtecSquJiWJT9GsYL5dl3Hr6ZYhxqs1-mrrB5FNzn-NPclPSu9PANtQ1BDuahKy683/t85F8yjug5C5paamNfgiJgOm5Vi/USUmWeVmH_htZoYGJTbOywDkRT1bYp9JIxlWHA29MInhWNrdlxZ_1h-SQ3fM6pzKIoJ0m_T/KXYERPzle0cy_/OnlfIa-yUgBnx_slQ1f9h0AS/PVMv/yZ6W"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "eypn1gEV3BnckI3jcYzUvliSbukvxzO5acvE?ey8VEaR0lmpRa4IXv02fDPnlfdukWtb1/p-nlQlPVGnB52f9KwY4q98aVghXZqoit4AeFxMAHcCytOj61o8lHdcUF9fIcyF2HaFIk/k3Hdt7pS/5rb2eeWcEvc-5XB0T_Oh68AtCG8mOPpwKvzrhhIuEJck3vtFncgbDrSxg5mKkw924rMLP3Tb5tgIRuawZLwBxJL/qVIhAzfDGiIeNTzYOB9zHfHlfw3aZ1i/terePSN5EafVJ1yYw1KRXWL9/kPdAO0yFwSv3mUWx04oIIUURG6QKwO0rk7L0eAxnu4djnSXtqdvH_G50H1SSwwfKUg2Xz25-OZLkhxiaxEMMMY=3x0Yjhs7O1KFkI5gUQKH_VYAU2bJSAqpCKsxaYrdw91wUoya5rflCBVDHjC/BsezIkPFFmEu7sqs3WJg6dZeAiguYx7uZtDx1ILH18f29q9o34bM9SNolZNcG3fN7L2eWjilbmUq/Ty2545WkbHTjlcjLlHPAAjzLebfcFnlMSKH9Tqb/qx3G1z8wfzMa3dn3iRqNHwfmGOmfgK7RjtlZwoVruMjDWEza/o8imZF513yM7FrHTJkTFa1JjVbjU/C85ItZTiJsBUKAt/DbLg6W7lieKgHbgmz3cuwgVR7YDLZJB056TRcU9wrV0SUYDz0gogrpOEnZxdo4fb5UcCllj/AD/dYsfqVSHtTxKWBhun9Iqmx8FjgPtFCFugTxfaaHZ9dUC7TPahdSxixGvnu8EEvAs0Te85eJ9iyeq628Tvboz9J7KMq/uwflJtecSquJiWJT9GsYL5dl3Hr6ZYhxqs1-mrrB5FNzn-NPclPSu9PANtQ1BDuahKy683/t85F8yjug5C5paamNfgiJgOm5Vi/USUmWeVmH_htZoYGJTbOywDkRT1bYp9JIxlWHA29MInhWNrdlxZ_1h-SQ3fM6pzKIoJ0m_T/KXYERPzle0cy_/OnlfIa-yUgBnx_slQ1f9h0AS/PVMv/yZ6W"
-)
-
-func TestCloudConvert_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudelements/cloudelements.go b/pkg/detectors/cloudelements/cloudelements.go
deleted file mode 100644
index c7c4982f46fb..000000000000
--- a/pkg/detectors/cloudelements/cloudelements.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package cloudelements
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloudelements"}) + `\b([a-zA-Z0-9]{43})\b`)
- orgPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloudelements"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"cloudelements"}
-}
-
-// FromData will find and optionally verify CloudElements secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- orgMatches := orgPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, orgMatch := range orgMatches {
-
- resOrgMatch := strings.TrimSpace(orgMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CloudElements,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resOrgMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://staging.cloud-elements.com/elements/api-v2/accounts", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("User %s, Organization %s", resMatch, resOrgMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CloudElements
-}
-
-func (s Scanner) Description() string {
- return "CloudElements is an API integration platform that enables developers to connect their applications with various cloud services. CloudElements credentials can be used to access and manage these integrations."
-}
diff --git a/pkg/detectors/cloudelements/cloudelements_integration_test.go b/pkg/detectors/cloudelements/cloudelements_integration_test.go
deleted file mode 100644
index 6fa4314ddd17..000000000000
--- a/pkg/detectors/cloudelements/cloudelements_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cloudelements
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCloudElements_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLOUDELEMENTS")
- org := testSecrets.MustGetField("CLOUDELEMENTS_ORG")
- inactiveSecret := testSecrets.MustGetField("CLOUDELEMENTS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudelements secret %s within cloudelements %s", secret, org)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudElements,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudelements secret %s within cloudelements %s but not valid", inactiveSecret, org)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudElements,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CloudElements.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CloudElements.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudelements/cloudelements_test.go b/pkg/detectors/cloudelements/cloudelements_test.go
deleted file mode 100644
index 9cda22b9e869..000000000000
--- a/pkg/detectors/cloudelements/cloudelements_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package cloudelements
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- base_url: "https://api.example.com/v1/user"
- cloudelements_key: "4NloL5EzH3PLvNzjCMikofUfKXYOsYOeJBopEyDScIL"
- cloudelements_org: "inz9qofvjwnx59hgefq9sy5v64ilqrnu"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "4NloL5EzH3PLvNzjCMikofUfKXYOsYOeJBopEyDScILinz9qofvjwnx59hgefq9sy5v64ilqrnu"
-)
-
-func TestCloudElements_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudflareapitoken/cloudflareapitoken.go b/pkg/detectors/cloudflareapitoken/cloudflareapitoken.go
deleted file mode 100644
index 1e347c56a424..000000000000
--- a/pkg/detectors/cloudflareapitoken/cloudflareapitoken.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package cloudflareapitoken
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloudflare"}) + `\b([A-Za-z0-9_-]{40})\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{"cloudflare"}
-}
-
-// FromData will find and optionally verify CloudflareApiToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CloudflareApiToken,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.cloudflare.com/client/v4/user/tokens/verify", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CloudflareApiToken
-}
-
-func (s Scanner) Description() string {
- return "Cloudflare is a web infrastructure and website security company, providing content delivery network services, DDoS mitigation, Internet security, and distributed domain name server services. Cloudflare API tokens can be used to manage and interact with Cloudflare services."
-}
diff --git a/pkg/detectors/cloudflareapitoken/cloudflareapitoken_integration_test.go b/pkg/detectors/cloudflareapitoken/cloudflareapitoken_integration_test.go
deleted file mode 100644
index 05660e2c56a8..000000000000
--- a/pkg/detectors/cloudflareapitoken/cloudflareapitoken_integration_test.go
+++ /dev/null
@@ -1,137 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cloudflareapitoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCloudflareApiToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLOUDFLARE_API_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CLOUDFLARE_API_INACTIVE")
- secret2 := testSecrets.MustGetField("CLOUDFLARE_API_TOKEN2")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudflareapitoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudflareApiToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudflareapitoken secret %s within", secret2)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudflareApiToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudflareapitoken secret %s within but unverified", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudflareApiToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CloudflareApiToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CloudflareApiToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudflareapitoken/cloudflareapitoken_test.go b/pkg/detectors/cloudflareapitoken/cloudflareapitoken_test.go
deleted file mode 100644
index 22417496cff3..000000000000
--- a/pkg/detectors/cloudflareapitoken/cloudflareapitoken_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package cloudflareapitoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- base_url: "https://api.example.com/v1/user"
- cloudflare_token: "kOjD1yceduu2jxL2uuwT9dkOIudU3_54sLCEud6j"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "kOjD1yceduu2jxL2uuwT9dkOIudU3_54sLCEud6j"
-)
-
-func TestCloudFlareAPIToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudflarecakey/cloudflarecakey.go b/pkg/detectors/cloudflarecakey/cloudflarecakey.go
deleted file mode 100644
index dfe21b52ed3f..000000000000
--- a/pkg/detectors/cloudflarecakey/cloudflarecakey.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package cloudflarecakey
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // origin ca keys documentation: https://developers.cloudflare.com/fundamentals/api/get-started/ca-keys/
- keyPat = regexp.MustCompile(`\b(v1\.0-[A-Za-z0-9-]{171})\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{"cloudflare"}
-}
-
-// FromData will find and optionally verify CloudflareCaKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[matches[1]] = struct{}{}
- }
-
- for caKey := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CloudflareCaKey,
- Raw: []byte(caKey),
- }
-
- if verify {
- isVerified, verificationErr := verifyCloudFlareCAKey(ctx, client, caKey)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, caKey)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CloudflareCaKey
-}
-
-func (s Scanner) Description() string {
- return "Cloudflare is a web infrastructure and website security company. Cloudflare CA keys can be used to manage SSL/TLS certificates and other security settings."
-}
-
-func verifyCloudFlareCAKey(ctx context.Context, client *http.Client, caKey string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.cloudflare.com/client/v4/certificates?zone_id=a", nil)
- if err != nil {
- return false, nil
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("user-agent", "curl/7.68.0") // pretend to be from curl so we do not wait 100+ seconds -> nice try did not work
-
- req.Header.Add("X-Auth-User-Service-Key", caKey)
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/cloudflarecakey/cloudflarecakey_integration_test.go b/pkg/detectors/cloudflarecakey/cloudflarecakey_integration_test.go
deleted file mode 100644
index 0eedf05b9ff9..000000000000
--- a/pkg/detectors/cloudflarecakey/cloudflarecakey_integration_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cloudflarecakey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCloudflareCaKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLOUDFLARE_ORIGIN_CA_KEY")
- inactiveSecret := testSecrets.MustGetField("CLOUDFLARE_ORIGIN_CA_KEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudflarecakey secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudflareCaKey,
- Verified: true,
- },
- },
- wantErr: false,
- },
-
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudflarecakey secret %s within but unverified", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudflareCaKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
-
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CloudflareCaKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CloudflareCaKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudflarecakey/cloudflarecakey_test.go b/pkg/detectors/cloudflarecakey/cloudflarecakey_test.go
deleted file mode 100644
index 450fc6258630..000000000000
--- a/pkg/detectors/cloudflarecakey/cloudflarecakey_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package cloudflarecakey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- base_url: "https://api.cloudflare.com/v1/user"
- ca_key: "v1.0-13vvv5141b975834504fc75b-a670d21e1e012816c3c8d9745e2693adc2d2ec7c402f607dbf7f2bd5de3bdb490cce4420ef13179957c5651e1ee5d952b1e03bd0271e2b43a9847f0713f4d3942cde4a7bc2e4770615"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "v1.0-13vvv5141b975834504fc75b-a670d21e1e012816c3c8d9745e2693adc2d2ec7c402f607dbf7f2bd5de3bdb490cce4420ef13179957c5651e1ee5d952b1e03bd0271e2b43a9847f0713f4d3942cde4a7bc2e4770615"
-)
-
-func TestCloudFlareCAKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey.go b/pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey.go
deleted file mode 100644
index b44853f0f428..000000000000
--- a/pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package cloudflareglobalapikey
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- apiKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloudflare"}) + `\b([A-Za-z0-9_-]{37})\b`)
-
- emailPat = regexp.MustCompile(common.EmailPattern)
-)
-
-// 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{"cloudflare"}
-}
-
-// FromData will find and optionally verify CloudflareGlobalApiKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- apiKeyMatches := apiKeyPat.FindAllStringSubmatch(dataStr, -1)
-
- uniqueEmailMatches := make(map[string]struct{})
- for _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for _, apiKeyMatch := range apiKeyMatches {
- apiKeyRes := strings.TrimSpace(apiKeyMatch[1])
-
- for emailMatch := range uniqueEmailMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CloudflareGlobalApiKey,
- Redacted: emailMatch,
- Raw: []byte(apiKeyRes),
- RawV2: []byte(apiKeyRes + emailMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.cloudflare.com/client/v4/user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-Auth-Email", emailMatch)
- req.Header.Add("X-Auth-Key", apiKeyRes)
- req.Header.Add("Content-Type", "application/json")
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CloudflareGlobalApiKey
-}
-
-func (s Scanner) Description() string {
- return "Cloudflare is a web infrastructure and website security company. Its services include content delivery network (CDN), DDoS mitigation, Internet security, and distributed domain name server (DNS) services. Cloudflare API keys can be used to access and modify these services."
-}
diff --git a/pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey_integration_test.go b/pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey_integration_test.go
deleted file mode 100644
index 49a96a617fa4..000000000000
--- a/pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey_integration_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cloudflareglobalapikey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCloudflareGlobalApiKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- globalApiKey := testSecrets.MustGetField("CLOUDFLARE_GLOBAL_API_KEY")
- globalApiKeyEmail := testSecrets.MustGetField("CLOUDFLARE_GLOBAL_API_KEY_EMAIL")
- inactiveglobalApiKey := testSecrets.MustGetField("CLOUDFLARE_GLOBAL_API_KEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudflare globalapikey secret %s within with email %s", globalApiKey, globalApiKeyEmail)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudflareGlobalApiKey,
- Redacted: globalApiKeyEmail,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudflare globalapikey secret %s with email %s within but unverified", inactiveglobalApiKey, globalApiKeyEmail)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudflareGlobalApiKey,
- Redacted: globalApiKeyEmail,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CloudflareGlobalApiKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CloudflareGlobalApiKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey_test.go b/pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey_test.go
deleted file mode 100644
index fef882a30b73..000000000000
--- a/pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package cloudflareglobalapikey
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "abcD123efg456HIJklmn789OPQ_rstUVWxYZ-012 / testuser1005@example.com"
- invalidPattern = "abcD123efg456HIJklmn789OPQ_rstUVWxYZ-012/testing@go"
-)
-
-func TestCloudFlareGlobalAPIKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: fmt.Sprintf("cloudflare: %s", validPattern),
- want: []string{"abcD123efg456HIJklmn789OPQ_rstUVWxYZ-testuser1005@example.com"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("cloudflare keyword is not close to the real key and id = %s", validPattern),
- want: nil,
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("cloudflare: %s", invalidPattern),
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 && test.want != nil {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- t.Errorf("expected %d results, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudimage/cloudimage.go b/pkg/detectors/cloudimage/cloudimage.go
deleted file mode 100644
index d04e31f2df7a..000000000000
--- a/pkg/detectors/cloudimage/cloudimage.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package cloudimage
-
-import (
- "context"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloudimage"}) + `\b([a-z0-9_]{30})\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{"cloudimage"}
-}
-
-// FromData will find and optionally verify CloudImage secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CloudImage,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{"scope":"urls","urls":["/sample.li/paris.jpg?width=400","/sample.li/flat.jpg?width=400"]}
- `)
- timeout := 10 * time.Second
- client.Timeout = timeout
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.cloudimage.com/invalidate", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-Client-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CloudImage
-}
-
-func (s Scanner) Description() string {
- return "CloudImage is a service that provides image optimization and delivery. CloudImage API keys can be used to access and modify image data."
-}
diff --git a/pkg/detectors/cloudimage/cloudimage_integration_test.go b/pkg/detectors/cloudimage/cloudimage_integration_test.go
deleted file mode 100644
index 00e00f173353..000000000000
--- a/pkg/detectors/cloudimage/cloudimage_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cloudimage
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCloudImage_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLOUDIMAGE")
- inactiveSecret := testSecrets.MustGetField("CLOUDIMAGE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudimage secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudImage,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudimage secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CloudImage,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CloudImage.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CloudImage.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudimage/cloudimage_test.go b/pkg/detectors/cloudimage/cloudimage_test.go
deleted file mode 100644
index 0c329a4ecb90..000000000000
--- a/pkg/detectors/cloudimage/cloudimage_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package cloudimage
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- base_url: "https://api.example.com/v1/user"
- cloudimage: "d__9rvli8sm4jo18v5q0q4n7vhkwbv"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "d__9rvli8sm4jo18v5q0q4n7vhkwbv"
-)
-
-func TestCloudImage_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudmersive/cloudmersive.go b/pkg/detectors/cloudmersive/cloudmersive.go
deleted file mode 100644
index 85d0abd2cf4a..000000000000
--- a/pkg/detectors/cloudmersive/cloudmersive.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package cloudmersive
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloudmersive"}) + `\b([a-z0-9-]{36})\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{"cloudmersive"}
-}
-
-// FromData will find and optionally verify Cloudmersive secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Cloudmersive,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{"AddressString":"string","CapitalizationMode":"string"}`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.cloudmersive.com/validate/address/parse", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Apikey", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Cloudmersive
-}
-
-func (s Scanner) Description() string {
- return "Cloudmersive provides a suite of APIs for data validation, conversion, and security. Cloudmersive API keys can be used to access these services."
-}
diff --git a/pkg/detectors/cloudmersive/cloudmersive_integration_test.go b/pkg/detectors/cloudmersive/cloudmersive_integration_test.go
deleted file mode 100644
index e0acea511c66..000000000000
--- a/pkg/detectors/cloudmersive/cloudmersive_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cloudmersive
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCloudmersive_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLOUDMERSIVE")
- inactiveSecret := testSecrets.MustGetField("CLOUDMERSIVE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudmersive secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cloudmersive,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudmersive secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cloudmersive,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Cloudmersive.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Cloudmersive.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudmersive/cloudmersive_test.go b/pkg/detectors/cloudmersive/cloudmersive_test.go
deleted file mode 100644
index c8187c1fb6e9..000000000000
--- a/pkg/detectors/cloudmersive/cloudmersive_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package cloudmersive
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- base_url: "https://api.example.com/v1/user"
- cloudmersive: "sxk5k1nfra8jak0mjjc6afr6v-6gsf7dr9o1"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "sxk5k1nfra8jak0mjjc6afr6v-6gsf7dr9o1"
-)
-
-func TestCloudMersive_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudplan/cloudplan.go b/pkg/detectors/cloudplan/cloudplan.go
deleted file mode 100644
index 3c94e2021718..000000000000
--- a/pkg/detectors/cloudplan/cloudplan.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package cloudplan
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloudplan"}) + `\b([A-Z0-9-]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"cloudplan"}
-}
-
-// FromData will find and optionally verify Cloudplan secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Cloudplan,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.cloudplan.biz/api/user/me", nil)
- if err != nil {
- continue
- }
- req.Header.Add("session_id", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Cloudplan
-}
-
-func (s Scanner) Description() string {
- return "Cloudplan is a service that offers cloud-based business solutions. Cloudplan session IDs can be used to access and manage user sessions and data."
-}
diff --git a/pkg/detectors/cloudplan/cloudplan_integration_test.go b/pkg/detectors/cloudplan/cloudplan_integration_test.go
deleted file mode 100644
index 531d959b4c5f..000000000000
--- a/pkg/detectors/cloudplan/cloudplan_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cloudplan
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCloudplan_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLOUDPLAN")
- inactiveSecret := testSecrets.MustGetField("CLOUDPLAN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudplan secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cloudplan,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudplan secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cloudplan,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Cloudplan.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Cloudplan.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudplan/cloudplan_test.go b/pkg/detectors/cloudplan/cloudplan_test.go
deleted file mode 100644
index ab2f5c9b5dc2..000000000000
--- a/pkg/detectors/cloudplan/cloudplan_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package cloudplan
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- base_url: "https://api.example.com/v1/user"
- cloudplan_session_key: "Y6D1FIS3XZXIJLKD82P6U8IXYV4UEYPP"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "Y6D1FIS3XZXIJLKD82P6U8IXYV4UEYPP"
-)
-
-func TestCloudPlan_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudsmith/cloudsmith.go b/pkg/detectors/cloudsmith/cloudsmith.go
deleted file mode 100644
index 157d7184c99b..000000000000
--- a/pkg/detectors/cloudsmith/cloudsmith.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package cloudsmith
-
-import (
- "context"
- "encoding/json"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloudsmith"}) + `\b([0-9a-f]{40})\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{"cloudsmith"}
-}
-
-type response struct {
- Authenticated bool `json:"authenticated"`
-}
-
-// FromData will find and optionally verify Cloudsmith secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Cloudsmith,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.cloudsmith.io/v1/user/self/", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("X-Api-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- var r response
- if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
- s1.SetVerificationError(err, resMatch)
- continue
- }
- if r.Authenticated {
- s1.Verified = true
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Cloudsmith
-}
-
-func (s Scanner) Description() string {
- return "Cloudsmith is a cloud-native package management service. Cloudsmith API keys can be used to manage and distribute packages."
-}
diff --git a/pkg/detectors/cloudsmith/cloudsmith_integration_test.go b/pkg/detectors/cloudsmith/cloudsmith_integration_test.go
deleted file mode 100644
index 58fe57574567..000000000000
--- a/pkg/detectors/cloudsmith/cloudsmith_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cloudsmith
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCloudsmith_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLOUDSMITH")
- inactiveSecret := testSecrets.MustGetField("CLOUDSMITH_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudsmith secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cloudsmith,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloudsmith secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cloudsmith,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Cloudsmith.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Cloudsmith.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cloudsmith/cloudsmith_test.go b/pkg/detectors/cloudsmith/cloudsmith_test.go
deleted file mode 100644
index 86aa492dd2b3..000000000000
--- a/pkg/detectors/cloudsmith/cloudsmith_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package cloudsmith
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "X-API-Key"
- base_url: "https://api.example.com/v1/user"
- cloudsmith: "6fd00a2cfd7bbc51e1c4db6ac2f29d59629afd22"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "6fd00a2cfd7bbc51e1c4db6ac2f29d59629afd22"
-)
-
-func TestCloudSmith_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloverly/cloverly.go b/pkg/detectors/cloverly/cloverly.go
deleted file mode 100644
index 3e775a95d28d..000000000000
--- a/pkg/detectors/cloverly/cloverly.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package cloverly
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloverly"}) + `\b([a-z0-9:_]{28})\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{"cloverly"}
-}
-
-// FromData will find and optionally verify Cloverly secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Cloverly,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.cloverly.com/2019-03-beta/account", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Cloverly
-}
-
-func (s Scanner) Description() string {
- return "Cloverly is a platform that allows businesses to integrate carbon offsetting into their products and services. Cloverly API keys can be used to access and manage these offsetting services."
-}
diff --git a/pkg/detectors/cloverly/cloverly_integration_test.go b/pkg/detectors/cloverly/cloverly_integration_test.go
deleted file mode 100644
index 38d6b72a0a2b..000000000000
--- a/pkg/detectors/cloverly/cloverly_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cloverly
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCloverly_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLOVERLY")
- inactiveSecret := testSecrets.MustGetField("CLOVERLY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloverly secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cloverly,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloverly secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cloverly,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Cloverly.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Cloverly.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cloverly/cloverly_test.go b/pkg/detectors/cloverly/cloverly_test.go
deleted file mode 100644
index c1929ed64aa4..000000000000
--- a/pkg/detectors/cloverly/cloverly_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package cloverly
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- base_url: "https://api.example.com/v1/user"
- cloverly_token: "564i_0a9_v58bn:p9st3r3cgi95_"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "564i_0a9_v58bn:p9st3r3cgi95_"
-)
-
-func TestCloverly_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloze/cloze.go b/pkg/detectors/cloze/cloze.go
deleted file mode 100644
index a0f0d717eeac..000000000000
--- a/pkg/detectors/cloze/cloze.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package cloze
-
-import (
- "context"
- "net/http"
- "net/url"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloze"}) + `\b([0-9a-f]{32})\b`)
- emailPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cloze"}) + common.EmailPattern)
-)
-
-// 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{"cloze"}
-}
-
-// FromData will find and optionally verify Cloze secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- uniqueEmailMatches := make(map[string]struct{})
- for _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for emailMatch := range uniqueEmailMatches {
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Cloze,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := url.Values{}
- payload.Add("user", emailMatch)
- payload.Add("api_key", resMatch)
-
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.cloze.com/v1/profile?"+payload.Encode(), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Cloze
-}
-
-func (s Scanner) Description() string {
- return "Cloze is a relationship management tool that helps users manage their connections and interactions. Cloze API keys can be used to access and manage user data and interactions."
-}
diff --git a/pkg/detectors/cloze/cloze_integration_test.go b/pkg/detectors/cloze/cloze_integration_test.go
deleted file mode 100644
index 73733f5edd97..000000000000
--- a/pkg/detectors/cloze/cloze_integration_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cloze
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCloze_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- email := testSecrets.MustGetField("CLOZE_EMAIL")
- secret := testSecrets.MustGetField("CLOZE")
- inactiveSecret := testSecrets.MustGetField("CLOZE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloze user %s with cloze secret %s within", email, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cloze,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cloze user %s with cloze secret %s within but not valid", email, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Cloze,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Cloze.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Cloze.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/cloze/cloze_test.go b/pkg/detectors/cloze/cloze_test.go
deleted file mode 100644
index 71ba2d172071..000000000000
--- a/pkg/detectors/cloze/cloze_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package cloze
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d / testuser1005@example.com"
- invalidPattern = "abcD123efg456HIJklmn789OPQ_rstUVWxYZ-012/testing@go"
-)
-
-func TestCloze_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: fmt.Sprintf("cloze: %s", validPattern),
- want: []string{"1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("cloze keyword is not close to the real key and id = %s", validPattern),
- want: nil,
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("cloze: %s", invalidPattern),
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 && test.want != nil {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- t.Errorf("expected %d results, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/clustdoc/clustdoc.go b/pkg/detectors/clustdoc/clustdoc.go
deleted file mode 100644
index ed178a73b223..000000000000
--- a/pkg/detectors/clustdoc/clustdoc.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package clustdoc
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"clustdoc"}) + `\b([0-9a-zA-Z]{60})\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{"clustdoc"}
-}
-
-// FromData will find and optionally verify ClustDoc secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ClustDoc,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://clustdoc.com/api/users", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ClustDoc
-}
-
-func (s Scanner) Description() string {
- return "ClustDoc is a document management platform. ClustDoc API keys can be used to access and manage documents and workflows within the ClustDoc platform."
-}
diff --git a/pkg/detectors/clustdoc/clustdoc_integration_test.go b/pkg/detectors/clustdoc/clustdoc_integration_test.go
deleted file mode 100644
index 35802efcedd5..000000000000
--- a/pkg/detectors/clustdoc/clustdoc_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package clustdoc
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestClustDoc_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CLUSTDOC")
- inactiveSecret := testSecrets.MustGetField("CLUSTDOC_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clustdoc secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ClustDoc,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a clustdoc secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ClustDoc,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ClustDoc.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ClustDoc.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/clustdoc/clustdoc_test.go b/pkg/detectors/clustdoc/clustdoc_test.go
deleted file mode 100644
index d3f08c59a866..000000000000
--- a/pkg/detectors/clustdoc/clustdoc_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package clustdoc
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- base_url: "https://api.example.com/v1/user"
- clustdoc_token: "yQ7mTTO4eJ4I9GHDEdzF3wq0KVowNKjPMud3q0ZqaEIuoR1qCrARUyLwknNP"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "yQ7mTTO4eJ4I9GHDEdzF3wq0KVowNKjPMud3q0ZqaEIuoR1qCrARUyLwknNP"
-)
-
-func TestClustDoc_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/coda/coda.go b/pkg/detectors/coda/coda.go
deleted file mode 100644
index 34ab2cbd1eff..000000000000
--- a/pkg/detectors/coda/coda.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package coda
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"coda"}) + `\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\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{"coda"}
-}
-
-// FromData will find and optionally verify Coda secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Coda,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- req, err := http.NewRequestWithContext(ctx, "GET", "https://coda.io/apis/v1/whoami", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %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 if res.StatusCode == 401 {
- // The secret is determinately not verified (nothing to do)
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Coda
-}
-
-func (s Scanner) Description() string {
- return "Coda is a platform for building collaborative documents and applications. Coda API keys can be used to access and manipulate data within Coda documents and applications."
-}
diff --git a/pkg/detectors/coda/coda_integration_test.go b/pkg/detectors/coda/coda_integration_test.go
deleted file mode 100644
index 4b38faab82ca..000000000000
--- a/pkg/detectors/coda/coda_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package coda
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCoda_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CODA")
- inactiveSecret := testSecrets.MustGetField("CODA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coda secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coda,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coda secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coda,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coda secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coda,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coda secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coda,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Coda.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Coda.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/coda/coda_test.go b/pkg/detectors/coda/coda_test.go
deleted file mode 100644
index 217b783ad2b7..000000000000
--- a/pkg/detectors/coda/coda_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package coda
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- base_url: "https://api.example.com/v1/user"
- coda_token: "64ukni4l-zub4-3coe-html-9byb40oi5i87"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "64ukni4l-zub4-3coe-html-9byb40oi5i87"
-)
-
-func TestCoda_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/codacy/codacy.go b/pkg/detectors/codacy/codacy.go
deleted file mode 100644
index fa2b3197d236..000000000000
--- a/pkg/detectors/codacy/codacy.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package codacy
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"codacy"}) + `\b([0-9A-Za-z]{20})\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{"codacy"}
-}
-
-// FromData will find and optionally verify Codacy secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Codacy,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://app.codacy.com/api/v3/user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("api-token", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Codacy
-}
-
-func (s Scanner) Description() string {
- return "Codacy is an automated code review tool that helps developers and teams improve code quality. Codacy API tokens can be used to access and manage code quality reports and settings."
-}
diff --git a/pkg/detectors/codacy/codacy_integration_test.go b/pkg/detectors/codacy/codacy_integration_test.go
deleted file mode 100644
index d086024efb8e..000000000000
--- a/pkg/detectors/codacy/codacy_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package codacy
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCodacy_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CODACY")
- inactiveSecret := testSecrets.MustGetField("CODACY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a codacy secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Codacy,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a codacy secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Codacy,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Codacy.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Codacy.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/codacy/codacy_test.go b/pkg/detectors/codacy/codacy_test.go
deleted file mode 100644
index f416a30e3809..000000000000
--- a/pkg/detectors/codacy/codacy_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package codacy
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- base_url: "https://api.example.com/v1/user"
- codacy_token: "g73RSmTIzTU1wUA5BXYI"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "g73RSmTIzTU1wUA5BXYI"
-)
-
-func TestCodacy_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/codeclimate/codeclimate.go b/pkg/detectors/codeclimate/codeclimate.go
deleted file mode 100644
index 791b1d1d5885..000000000000
--- a/pkg/detectors/codeclimate/codeclimate.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package codeclimate
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"codeclimate"}) + `\b([a-f0-9]{40})\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{"codeclimate"}
-}
-
-type response struct {
- Data struct {
- Id string `json:"id"`
- } `json:"data"`
-}
-
-// FromData will find and optionally verify Codeclimate secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Codeclimate,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.codeclimate.com/v1/user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.api+json")
- req.Header.Add("Authorization", fmt.Sprintf("Token token=%s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- var r response
- if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
- s1.SetVerificationError(err, resMatch)
- continue
- }
- if r.Data.Id != "" {
- s1.Verified = true
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Codeclimate
-}
-
-func (s Scanner) Description() string {
- return "Codeclimate is a tool for automated code review and analysis. Codeclimate tokens can be used to access and manage repositories and their analysis results."
-}
diff --git a/pkg/detectors/codeclimate/codeclimate_integration_test.go b/pkg/detectors/codeclimate/codeclimate_integration_test.go
deleted file mode 100644
index 3a2fcf1b15fa..000000000000
--- a/pkg/detectors/codeclimate/codeclimate_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package codeclimate
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCodeclimate_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CODECLIMATE")
- inactiveSecret := testSecrets.MustGetField("CODECLIMATE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a codeclimate secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Codeclimate,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a codeclimate secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Codeclimate,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Codeclimate.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Codeclimate.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/codeclimate/codeclimate_test.go b/pkg/detectors/codeclimate/codeclimate_test.go
deleted file mode 100644
index 2a4c6b0ac892..000000000000
--- a/pkg/detectors/codeclimate/codeclimate_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package codeclimate
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- base_url: "https://api.example.com/v1/user"
- codeclimate_token: "efbc069555c703d31c3bcc6fbd426cec5f21eb43"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "efbc069555c703d31c3bcc6fbd426cec5f21eb43"
-)
-
-func TestCodeClimate_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/codemagic/codemagic.go b/pkg/detectors/codemagic/codemagic.go
deleted file mode 100644
index d3a455a9e92b..000000000000
--- a/pkg/detectors/codemagic/codemagic.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package codemagic
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"codemagic"}) + common.BuildRegex(common.AlphaNumPattern, "_", 43))
-)
-
-// 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{"codemagic"}
-}
-
-// FromData will find and optionally verify Codemagic secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Codemagic,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.codemagic.io/apps", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("x-auth-token", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Codemagic
-}
-
-func (s Scanner) Description() string {
- return "Codemagic is a CI/CD platform for mobile app projects. Codemagic API keys can be used to automate and manage the build and deployment process of mobile applications."
-}
diff --git a/pkg/detectors/codemagic/codemagic_integration_test.go b/pkg/detectors/codemagic/codemagic_integration_test.go
deleted file mode 100644
index e3ab148ae221..000000000000
--- a/pkg/detectors/codemagic/codemagic_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package codemagic
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCodemagic_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CODEMAGIC")
- inactiveSecret := testSecrets.MustGetField("CODEMAGIC_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a codemagic secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Codemagic,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a codemagic secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Codemagic,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Codemagic.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Codemagic.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/codemagic/codemagic_test.go b/pkg/detectors/codemagic/codemagic_test.go
deleted file mode 100644
index e110566af325..000000000000
--- a/pkg/detectors/codemagic/codemagic_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package codemagic
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Auth-Key"
- base_url: "https://api.example.com/v1/user"
- codemagic_key: "PSIYbVgfkbEPQoqJfzHpACTtihONkQ_cKmOpNDPNiCU"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "PSIYbVgfkbEPQoqJfzHpACTtihONkQ_cKmOpNDPNiCU"
-)
-
-func TestCodeMagic_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/codequiry/codequiry.go b/pkg/detectors/codequiry/codequiry.go
deleted file mode 100644
index d2a3cce7cb57..000000000000
--- a/pkg/detectors/codequiry/codequiry.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package codequiry
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"codequiry"}) + `\b([a-zA-Z-0-9]{64})\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{"codequiry"}
-}
-
-// FromData will find and optionally verify Codequiry secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Codequiry,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://codequiry.com/api/v1/checks", nil)
- if err != nil {
- continue
- }
-
- req.Header.Add("apikey", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Codequiry
-}
-
-func (s Scanner) Description() string {
- return "Codequiry is a plagiarism detection service. Codequiry API keys can be used to access and utilize their plagiarism detection features."
-}
diff --git a/pkg/detectors/codequiry/codequiry_integration_test.go b/pkg/detectors/codequiry/codequiry_integration_test.go
deleted file mode 100644
index 30e9e1751eab..000000000000
--- a/pkg/detectors/codequiry/codequiry_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package codequiry
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCodequiry_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CODEQUIRY")
- inactiveSecret := testSecrets.MustGetField("CODEQUIRY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a codequiry secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Codequiry,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a codequiry secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Codequiry,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Codequiry.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Codequiry.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/codequiry/codequiry_test.go b/pkg/detectors/codequiry/codequiry_test.go
deleted file mode 100644
index 268555c38ace..000000000000
--- a/pkg/detectors/codequiry/codequiry_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package codequiry
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- base_url: "https://api.example.com/v1/user"
- codequiry_key: "7cA6eb3AvmlVSqLMP4iKvp7fXtEAADQud11KidjPzbSLcvntAD8CK6P7uGaGdlit"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "7cA6eb3AvmlVSqLMP4iKvp7fXtEAADQud11KidjPzbSLcvntAD8CK6P7uGaGdlit"
-)
-
-func TestCodeQuiry_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/coinapi/coinapi.go b/pkg/detectors/coinapi/coinapi.go
deleted file mode 100644
index dac83f2a78c6..000000000000
--- a/pkg/detectors/coinapi/coinapi.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package coinapi
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"coinapi"}) + `\b([A-Z0-9-]{36})\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{"coinapi"}
-}
-
-// FromData will find and optionally verify CoinApi secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CoinApi,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://rest.coinapi.io/v1/exchanges", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-CoinAPI-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CoinApi
-}
-
-func (s Scanner) Description() string {
- return "CoinApi provides a RESTful API to access cryptocurrency market data. CoinApi keys can be used to fetch real-time and historical cryptocurrency data."
-}
diff --git a/pkg/detectors/coinapi/coinapi_integration_test.go b/pkg/detectors/coinapi/coinapi_integration_test.go
deleted file mode 100644
index 9ee35f7f9d83..000000000000
--- a/pkg/detectors/coinapi/coinapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package coinapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCoinApi_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COINAPI")
- inactiveSecret := testSecrets.MustGetField("COINAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coinapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CoinApi,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coinapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CoinApi,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CoinApi.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CoinApi.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/coinapi/coinapi_test.go b/pkg/detectors/coinapi/coinapi_test.go
deleted file mode 100644
index a13dd6ce0f12..000000000000
--- a/pkg/detectors/coinapi/coinapi_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package coinapi
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- base_url: "https://api.example.com/v1/user"
- coinapi_key: "6D8B5AUIRDQCB3NRKSMZZL9RCV9G07GTHUR3"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "6D8B5AUIRDQCB3NRKSMZZL9RCV9G07GTHUR3"
-)
-
-func TestCoinAPI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/coinbase/coinbase.go b/pkg/detectors/coinbase/coinbase.go
deleted file mode 100644
index f683b1277209..000000000000
--- a/pkg/detectors/coinbase/coinbase.go
+++ /dev/null
@@ -1,206 +0,0 @@
-package coinbase
-
-import (
- "context"
- "crypto/ecdsa"
- "crypto/rand"
- "crypto/x509"
- "encoding/pem"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-
- "github.com/golang-jwt/jwt/v5"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Reference: https://docs.cdp.coinbase.com/coinbase-app/docs/auth/api-key-authentication
- keyNamePat = regexp.MustCompile(`\b(organizations\\*/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\\*/apiKeys\\*/\w{8}-\w{4}-\w{4}-\w{4}-\w{12})\b`)
- privateKeyPat = regexp.MustCompile(`(-----BEGIN EC(?:DSA)? PRIVATE KEY-----(?:\r|\n|\\+r|\\+n)(?:[a-zA-Z0-9+/]+={0,2}(?:\r|\n|\\+r|\\+n))+-----END EC(?:DSA)? PRIVATE KEY-----(?:\r|\n|\\+r|\\+n)?)`)
-
- apiHost = "api.coinbase.com"
- verificationEndpoint = "/v2/user"
- verificationMethod = http.MethodGet
- verificationURI = fmt.Sprintf("https://%s%s", apiHost, verificationEndpoint)
-
- nameReplacer = strings.NewReplacer("\\", "")
- keyReplacer = strings.NewReplacer(
- "\r\n", "\n",
- "\\r\\n", "\n",
- "\\n", "\n",
- "\\r", "\n",
- )
-)
-
-// 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{"begin ec"}
-}
-
-func isValidECPrivateKey(pemKey []byte) bool {
- block, _ := pem.Decode(pemKey)
- if block == nil {
- return false
- }
-
- key, err := x509.ParseECPrivateKey(block.Bytes)
- if err != nil {
- return false
- }
-
- // Check the key type
- _, ok := key.Public().(*ecdsa.PublicKey)
- return ok
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Coinbase secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueKeyNames, uniquePrivateKeys := map[string]struct{}{}, map[string]struct{}{}
-
- for _, keyNameMatch := range keyNamePat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeyNames[keyNameMatch[1]] = struct{}{}
- }
-
- for _, privateKeyMatch := range privateKeyPat.FindAllStringSubmatch(dataStr, -1) {
- uniquePrivateKeys[privateKeyMatch[1]] = struct{}{}
- }
-
- for keyName := range uniqueKeyNames {
- for privateKey := range uniquePrivateKeys {
- client := s.getClient()
- resKeyName := nameReplacer.Replace(strings.TrimSpace(keyName))
- resPrivateKey := keyReplacer.Replace(strings.TrimSpace(privateKey))
-
- if !isValidECPrivateKey([]byte(resPrivateKey)) {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Coinbase,
- Raw: []byte(resPrivateKey),
- RawV2: []byte(fmt.Sprintf("%s:%s", resKeyName, resPrivateKey)),
- }
-
- if verify {
- isVerified, verificationErr := s.verifyMatch(ctx, client, resKeyName, resPrivateKey)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resPrivateKey)
- }
- results = append(results, s1)
-
- // If we've found a verified match with this ID, we don't need to look for anymore. So move on to the next ID.
- if s1.Verified {
- break
- }
-
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) verifyMatch(ctx context.Context, client *http.Client, keyName, privateKey string) (bool, error) {
- jwtToken, err := buildJWT(verificationMethod, apiHost, verificationEndpoint, keyName, privateKey)
- if err != nil {
- return false, err
- }
- req, err := http.NewRequestWithContext(ctx, verificationMethod, verificationURI, http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", jwtToken))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
-
-// Coinbase API requires the credentials encoded in a JWT token
-// The JWT token is signed with the private key and expires in 2 minutes
-func buildJWT(method, host, endpoint, keyName, key string) (string, error) {
- // Decode the PEM key
- pemStr := strings.ReplaceAll(key, `\n`, "\n")
- block, _ := pem.Decode([]byte(pemStr))
- if block == nil || block.Type != "EC PRIVATE KEY" {
- return "", fmt.Errorf("failed to decode PEM block containing EC private key")
- }
-
- privateKey, err := x509.ParseECPrivateKey(block.Bytes)
- if err != nil {
- return "", fmt.Errorf("failed to parse EC private key: %v", err)
- }
-
- now := time.Now().Unix()
- claims := jwt.MapClaims{
- "sub": keyName,
- "iss": "cdp",
- "nbf": now,
- "exp": now + 120,
- "uri": fmt.Sprintf("%s %s%s", method, host, endpoint),
- }
-
- token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
- token.Header["kid"] = keyName
- token.Header["nonce"] = fmt.Sprintf("%x", makeNonce())
-
- signedToken, err := token.SignedString(privateKey)
- if err != nil {
- return "", fmt.Errorf("failed to sign JWT: %v", err)
- }
-
- return signedToken, nil
-}
-
-func makeNonce() []byte {
- nonce := make([]byte, 16) // 128-bit nonce
- _, _ = rand.Read(nonce)
- return nonce
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Coinbase
-}
-
-func (s Scanner) Description() string {
- return "Coinbase is a digital currency exchange that allows users to buy, sell, and store various cryptocurrencies. A Coinbase API key name and private key can be used to access and manage a user's account and transactions."
-}
diff --git a/pkg/detectors/coinbase/coinbase_integration_test.go b/pkg/detectors/coinbase/coinbase_integration_test.go
deleted file mode 100644
index d51e352f4b18..000000000000
--- a/pkg/detectors/coinbase/coinbase_integration_test.go
+++ /dev/null
@@ -1,166 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package coinbase
-
-import (
- "context"
- "fmt"
- "net/http"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCoinbase_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- keyName := testSecrets.MustGetField("COINBASE_KEY_NAME")
- privateKey := testSecrets.MustGetField("COINBASE_PRIVATE_KEY")
- inactiveKeyName := testSecrets.MustGetField("COINBASE_INACTIVE_KEY_NAME")
- inactivePrivateKey := testSecrets.MustGetField("COINBASE_INACTIVE_PRIVATE_KEY")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coinbase secret %s %s within", keyName, privateKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coinbase,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coinbase secret %s %s within but not valid", inactiveKeyName, inactivePrivateKey)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coinbase,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coinbase secret %s %s within", keyName, privateKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coinbase,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(http.StatusInternalServerError, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coinbase secret %s %s within", keyName, privateKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coinbase,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Coinbase.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "primarySecret")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Coinbase.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/coinbase/coinbase_test.go b/pkg/detectors/coinbase/coinbase_test.go
deleted file mode 100644
index 69666a93a6ca..000000000000
--- a/pkg/detectors/coinbase/coinbase_test.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package coinbase
-
-import (
- "context"
- "testing"
-)
-
-func TestCoinbase_Pattern(t *testing.T) {
- tests := []struct {
- name string
- data string
- shouldMatch bool
- match string
- }{
- // True positives
- // https://github.com/coinbase/waas-client-library-go/issues/41
- {
- name: "valid_result1",
- data: `{ "name": "organizations/14d1742b-3575-4490-b9bc-a8a9c7e4973d/apiKeys/7473d38c-80c6-4a69-a715-1ea8fd950f6f", "principal": "8feb538e-137b-5864-b12a-7c75b60fa20a", "principalType": "USER", "publicKey": "-----BEGIN EC PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzR0G+CW0uVJFrpLUELqB+DlsmGmO\nA03Az8Fpv7azpgjAy87ibgQTThaQy1C1BccbCDkPoEs6mOnDkOebkybAKQ==\n-----END EC PUBLIC KEY-----\n", "privateKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIBddyynZ9Ya7op1B9nu1Dxyc1T6xLy72t45J2Smv9oXNoAoGCCqGSM49\nAwEHoUQDQgAEzR0G+CW0uVJFrpLUELqB+DlsmGmOA03Az8Fpv7azpgjAy87ibgQT\nThaQy1C1BccbCDkPoEs6mOnDkOebkybAKQ==\n-----END EC PRIVATE KEY-----\n", "createTime": "2023-08-19T12:29:08.938421763Z", "projectId": "5970e137-9c3d-4adc-b65d-58d33af2432d" }`,
- shouldMatch: true,
- match: "organizations/14d1742b-3575-4490-b9bc-a8a9c7e4973d/apiKeys/7473d38c-80c6-4a69-a715-1ea8fd950f6f:-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIBddyynZ9Ya7op1B9nu1Dxyc1T6xLy72t45J2Smv9oXNoAoGCCqGSM49\nAwEHoUQDQgAEzR0G+CW0uVJFrpLUELqB+DlsmGmOA03Az8Fpv7azpgjAy87ibgQT\nThaQy1C1BccbCDkPoEs6mOnDkOebkybAKQ==\n-----END EC PRIVATE KEY-----\n",
- },
- // https://github.com/coinbase/waas-client-library-go/pull/32#issuecomment-1666415017
- {
- name: "valid_result2_name_slashes",
- data: `{
- "name": "organizations\/d3f266dc-0d36-4cd0-91c3-e3a292b0b4b3\/apiKeys\/032c4fdf-d763-4b0c-9ed3-ff41a873bcc8",
- "principal": "5d5c9f00-3224-52a7-a1f7-9e6ce3ada40c",
- "principalType": "USER",
- "publicKey": "-----BEGIN EC PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAjw43hwOqS2PF4gAFbhoxIJqCHAP\niqLdg5GFVn9QAS/0oY4/fJGrCn9rpQGOvHxHf1mtQ6j4bIWN1AtHvA/3uw==\n-----END EC PUBLIC KEY-----\n",
- "privateKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIFkA1kU4DlNu36wTTHycWy6n1rsUH0UT8mfAKNtOukXHoAoGCCqGSM49\nAwEHoUQDQgAEAjw43hwOqS2PF4gAFbhoxIJqCHAPiqLdg5GFVn9QAS/0oY4/fJGr\nCn9rpQGOvHxHf1mtQ6j4bIWN1AtHvA/3uw==\n-----END EC PRIVATE KEY-----\n",
- "createTime": "2023-08-05T06:34:40.265235553Z",
- "projectId": "64b3f391-c69d-4c59-91a2-75816c1a0738"
- }`,
- shouldMatch: true,
- match: "organizations/d3f266dc-0d36-4cd0-91c3-e3a292b0b4b3/apiKeys/032c4fdf-d763-4b0c-9ed3-ff41a873bcc8:-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIFkA1kU4DlNu36wTTHycWy6n1rsUH0UT8mfAKNtOukXHoAoGCCqGSM49\nAwEHoUQDQgAEAjw43hwOqS2PF4gAFbhoxIJqCHAPiqLdg5GFVn9QAS/0oY4/fJGr\nCn9rpQGOvHxHf1mtQ6j4bIWN1AtHvA/3uw==\n-----END EC PRIVATE KEY-----\n",
- },
- {
- name: "valid_result3",
- data: `name: "organizations/7eead2d5-fa48-4423-8f40-c70d8ce398ae/apiKeys/7b9516b6-d82e-44e8-bed5-89b160452ed9",
- description: "principal": "775fb863-004f-5412-8e4c-e9449c612563" and install dependencies
-
- runs: "principalType": "USER",
- using: composite
- steps:"publicKey": "-----BEGIN EC PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvHsvI08kox+n/8wSMFwCbK5hEf5b\n/g82Lmz3HpATKFmrICcOBX2lRHo99JWRrupmjUGxnD8i4sj4mZafTEokhA==\n-----END EC PUBLIC KEY-----\n",
- - name: Setup Node.js
- uses: actions/setup-node@v3
- with: "privateKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIKOQ7lvGL0EiUzZ23pmH/NBPRwVV8yZsqofds5bSR9qFoAoGCCqGSM49\nAwEHoUQDQgAEvHsvI08kox+n/8wSMFwCbK5hEf5b/g82Lmz3HpATKFmrICcOBX2l\nRHo99JWRrupmjUGxnD8i4sj4mZafTEokhA==\n-----END EC PRIVATE KEY-----\n",
- node-version-file: .nvmrc
-
- - name: Cache dependencies`,
- shouldMatch: true,
- match: "organizations/7eead2d5-fa48-4423-8f40-c70d8ce398ae/apiKeys/7b9516b6-d82e-44e8-bed5-89b160452ed9:-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIKOQ7lvGL0EiUzZ23pmH/NBPRwVV8yZsqofds5bSR9qFoAoGCCqGSM49\nAwEHoUQDQgAEvHsvI08kox+n/8wSMFwCbK5hEf5b/g82Lmz3HpATKFmrICcOBX2l\nRHo99JWRrupmjUGxnD8i4sj4mZafTEokhA==\n-----END EC PRIVATE KEY-----\n",
- },
- {
- name: "valid_result_ecdsa",
- data: `{
- "name": "organizations/7eead2d5-fa48-4423-8f40-c70d8ce398ae/apiKeys/7b9516b6-d82e-44e8-bed5-89b160452ed9",
- "privateKey": "-----BEGIN ECDSA PRIVATE KEY-----\nMHcCAQEEINQdZMbF2r07KF0mxfLYt9Y1PNaC0C6UpZ31MxD4NEE8oAoGCCqGSM49\nAwEHoUQDQgAEeRFgMrQEHI/APWaziRH90jN7EozjdbPVxvzc1F4zqWTeCtLASwqA\nqnMugYX2epqsFhGn82xNXu2NwgORc6embQ==\n-----END ECDSA PRIVATE KEY-----\n"
-}`,
- shouldMatch: true,
- match: "organizations/7eead2d5-fa48-4423-8f40-c70d8ce398ae/apiKeys/7b9516b6-d82e-44e8-bed5-89b160452ed9:-----BEGIN ECDSA PRIVATE KEY-----\nMHcCAQEEINQdZMbF2r07KF0mxfLYt9Y1PNaC0C6UpZ31MxD4NEE8oAoGCCqGSM49\nAwEHoUQDQgAEeRFgMrQEHI/APWaziRH90jN7EozjdbPVxvzc1F4zqWTeCtLASwqA\nqnMugYX2epqsFhGn82xNXu2NwgORc6embQ==\n-----END ECDSA PRIVATE KEY-----\n",
- },
- // TODO: Is it worth supporting case-insensitive headers?
- // https://github.com/coinbase/waas-sdk-react-native/blob/bbaf597e73d02ecaf64161061e71b85d9eeeb9d6/example/src/.coinbase_cloud_api_key.json#L4
- // {
- // name: "valid_result_case_insensitive",
- // data: `{
- // "name": "organizations/7eead2d5-fa48-4423-8f40-c70d8ce398ae/apiKeys/7b9516b6-d82e-44e8-bed5-89b160452ed9",
- // "privateKey": "-----BEGIN ECDSA private key-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8id7yCfmNp0ppczu\nDhjB1pesdDB6Uwuz6KxARrenNfyhRANCAASI6DBntdr+XSOaK55J++x8ORuDxn81\nENa0RmGFjTwu4vQcWcx5rrIWNh6b7FPxy6mrZl0n3rswEtZmUci8Y5HX\n-----END ECDSA PRIVATE KEY-----\n"
- //}`,
- // shouldMatch: true,
- // match: "organizations/7eegad2d5-fa48-4423-8f40-c70d8ce398ae/apiKeys/7b9516b6-d82e-44e8-bed5-89b160452ed9:-----BEGIN ECDSA PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8id7yCfmNp0ppczu\nDhjB1pesdDB6Uwuz6KxARrenNfyhRANCAASI6DBntdr+XSOaK55J++x8ORuDxn81\nENa0RmGFjTwu4vQcWcx5rrIWNh6b7FPxy6mrZl0n3rswEtZmUci8Y5HX\n-----END ECDSA PRIVATE KEY-----\n",
- // },
-
- // False positives
- // https://github.com/coinbase/waas-client-library-go/blob/main/example.go
- {
- name: `invalid_key_name1`,
- data: `const (
- // apiKeyName is the name of the API Key to use. Fill this out before running the main function.
- apiKeyName = "organizations/my-organization/apiKeys/my-api-key"
-
- // privKeyTemplate is the private key of the API Key to use. Fill this out before running the main function.
- privKeyTemplate = "-----BEGIN EC PRIVATE KEY-----\nmy-private-key\n-----END EC PRIVATE KEY-----\n"
-)`,
- shouldMatch: false,
- },
- // https://github.com/coinbase/waas-sdk-react-native/blob/bbaf597e73d02ecaf64161061e71b85d9eeeb9d6/example/src/.coinbase_cloud_api_key.json#L4
- {
- name: `invalid_key_name2`,
- data: `{
- "name": "organizations/organizationID/apiKeys/apiKeyName",
- "privateKey": "-----BEGIN ECDSA Private Key-----ExamplePrivateKey-----END ECDSA Private Key-----\n"
-}`,
- },
- {
- name: `invalid_private_key`,
- data: `{ "name": "organizations/14d1742b-3575-4490-b9bc-a8a9c7e4973d/apiKeys/7473d38c-80c6-4a69-a715-1ea8fd950f6f", "principal": "8feb538e-137b-5864-b12a-7c75b60fa20a", "principalType": "USER", "publicKey": "-----BEGIN EC PUBLIC KEY-----\ninvalid\n-----END EC PUBLIC KEY-----\n", "privateKey": "-----BEGIN EC PRIVATE KEY-----\ninvalid\n-----END EC PRIVATE KEY-----\n", "createTime": "2023-08-19T12:29:08.938421763Z", "projectId": "5970e137-9c3d-4adc-b65d-58d33af2432d" }`,
- },
- }
-
- 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("Coinbase.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.RawV2)
- 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
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/coinlayer/coinlayer.go b/pkg/detectors/coinlayer/coinlayer.go
deleted file mode 100644
index 280497e50516..000000000000
--- a/pkg/detectors/coinlayer/coinlayer.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package coinlayer
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"coinlayer"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"coinlayer"}
-}
-
-// FromData will find and optionally verify Coinlayer secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Coinlayer,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.coinlayer.com/api/livelive?access_key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err == nil {
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"success": true`) || strings.Contains(bodyString, `"info":"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption."`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Coinlayer
-}
-
-func (s Scanner) Description() string {
- return "Coinlayer provides real-time and historical cryptocurrency exchange rates. The API key can be used to access this data."
-}
diff --git a/pkg/detectors/coinlayer/coinlayer_integration_test.go b/pkg/detectors/coinlayer/coinlayer_integration_test.go
deleted file mode 100644
index 095501355744..000000000000
--- a/pkg/detectors/coinlayer/coinlayer_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package coinlayer
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCoinlayer_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COINLAYER")
- inactiveSecret := testSecrets.MustGetField("COINLAYER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coinlayer secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coinlayer,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coinlayer secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coinlayer,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Coinlayer.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Coinlayer.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/coinlayer/coinlayer_test.go b/pkg/detectors/coinlayer/coinlayer_test.go
deleted file mode 100644
index 15960cce9ea7..000000000000
--- a/pkg/detectors/coinlayer/coinlayer_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package coinlayer
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- base_url: "https://api.coinlayer.com/v1/user?key=gg2einqoe3zxu0ju7c3wqg9vql662vdj"
- key: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "gg2einqoe3zxu0ju7c3wqg9vql662vdj"
-)
-
-func TestCoinLayer_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/coinlib/coinlib.go b/pkg/detectors/coinlib/coinlib.go
deleted file mode 100644
index 949fe4480dc1..000000000000
--- a/pkg/detectors/coinlib/coinlib.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package coinlib
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"coinlib"}) + `\b([a-z0-9]{16})\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{"coinlib"}
-}
-
-// FromData will find and optionally verify Coinlib secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Coinlib,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://coinlib.io/api/v1/global?key=%s&pref=EUR", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Coinlib
-}
-
-func (s Scanner) Description() string {
- return "Coinlib is a cryptocurrency data provider. Coinlib API keys can be used to access and retrieve cryptocurrency data."
-}
diff --git a/pkg/detectors/coinlib/coinlib_integration_test.go b/pkg/detectors/coinlib/coinlib_integration_test.go
deleted file mode 100644
index a4955448778f..000000000000
--- a/pkg/detectors/coinlib/coinlib_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package coinlib
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCoinlib_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COINLIB")
- inactiveSecret := testSecrets.MustGetField("COINLIB_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coinlib secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coinlib,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coinlib secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coinlib,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Coinlib.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Coinlib.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/coinlib/coinlib_test.go b/pkg/detectors/coinlib/coinlib_test.go
deleted file mode 100644
index eba2c008a009..000000000000
--- a/pkg/detectors/coinlib/coinlib_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package coinlib
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- base_url: "https://api.coinlib.com/v1/user?key=seugeupfknprstoe"
- key: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "seugeupfknprstoe"
-)
-
-func TestCoinLib_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/collect2/collect2.go b/pkg/detectors/collect2/collect2.go
deleted file mode 100644
index 52e316d0d40a..000000000000
--- a/pkg/detectors/collect2/collect2.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package collect2
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"collect2"}) + `\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\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{"collect2"}
-}
-
-// FromData will find and optionally verify Collect2 secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Collect2,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://collect2.com/api/%s/datarecord/", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Collect2
-}
-
-func (s Scanner) Description() string {
- return "An API to Collect, Modify, Filter and Export Data using webhooks. API keys can create read update and delete data."
-}
diff --git a/pkg/detectors/collect2/collect2_integration_test.go b/pkg/detectors/collect2/collect2_integration_test.go
deleted file mode 100644
index ddd91500e1fc..000000000000
--- a/pkg/detectors/collect2/collect2_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package collect2
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCollect2_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COLLECT2")
- inactiveSecret := testSecrets.MustGetField("COLLECT2_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a collect2 secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Collect2,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a collect2 secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Collect2,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Collect2.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Collect2.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/collect2/collect2_test.go b/pkg/detectors/collect2/collect2_test.go
deleted file mode 100644
index c65c7e1dc301..000000000000
--- a/pkg/detectors/collect2/collect2_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package collect2
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- base_url: "https://api.collect2.com/v1/user?key=22f39f53-3bd4-d84b-8e29-00402d5c316f"
- key: ""
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "22f39f53-3bd4-d84b-8e29-00402d5c316f"
-)
-
-func TestCollect2_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/column/column.go b/pkg/detectors/column/column.go
deleted file mode 100644
index b33f862771be..000000000000
--- a/pkg/detectors/column/column.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package column
-
-import (
- "context"
- "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"column"}) + `\b((?:test|live)_[a-zA-Z0-9]{27})\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{"column"}
-}
-
-// FromData will find and optionally verify Column secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Column,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.column.com/entities", nil)
- if err != nil {
- continue
- }
- // req.SetBasicAuth(resMatch, "")
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(resMatch))))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Column
-}
-
-func (s Scanner) Description() string {
- return "Column is a service used for managing entity data. Column keys can be used to access and modify this data."
-}
diff --git a/pkg/detectors/column/column_integration_test.go b/pkg/detectors/column/column_integration_test.go
deleted file mode 100644
index 6e9b22e4f5cc..000000000000
--- a/pkg/detectors/column/column_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package column
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestColumn_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COLUMN_ACTIVE")
- inactiveSecret := testSecrets.MustGetField("COLUMN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a column secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Column,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a column secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Column,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Column.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Column.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/column/column_test.go b/pkg/detectors/column/column_test.go
deleted file mode 100644
index 8f19087d3866..000000000000
--- a/pkg/detectors/column/column_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package column
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- base_url: "https://api.example.com/v1/user"
- column_key: "live_ID8Jxlu0QRsV7rKkWI9CUDpkrUv"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "live_ID8Jxlu0QRsV7rKkWI9CUDpkrUv"
-)
-
-func TestColumn_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/commercejs/commercejs.go b/pkg/detectors/commercejs/commercejs.go
deleted file mode 100644
index 05de8861185a..000000000000
--- a/pkg/detectors/commercejs/commercejs.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package commercejs
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"commercejs"}) + `\b([a-z0-9_]{48})\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{"commercejs"}
-}
-
-// FromData will find and optionally verify CommerceJS secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CommerceJS,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.chec.io/v1/categories", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-Authorization", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CommerceJS
-}
-
-func (s Scanner) Description() string {
- return "CommerceJS is a headless commerce platform that provides APIs for building custom e-commerce experiences. CommerceJS API keys can be used to access and manage e-commerce functionalities."
-}
diff --git a/pkg/detectors/commercejs/commercejs_integration_test.go b/pkg/detectors/commercejs/commercejs_integration_test.go
deleted file mode 100644
index 0c39e067e92a..000000000000
--- a/pkg/detectors/commercejs/commercejs_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package commercejs
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCommerceJS_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COMMERCEJS")
- inactiveSecret := testSecrets.MustGetField("COMMERCEJS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a commercejs secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CommerceJS,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a commercejs secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CommerceJS,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CommerceJS.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CommerceJS.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/commercejs/commercejs_test.go b/pkg/detectors/commercejs/commercejs_test.go
deleted file mode 100644
index 57363764acfd..000000000000
--- a/pkg/detectors/commercejs/commercejs_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package commercejs
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- in: "Header"
- base_url: "https://api.example.com/v1/user"
- commercejs_key: "g6cl4jt_2noyibalgbqid4h58jxivqdgyyxovepbvqmbl7wq"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "g6cl4jt_2noyibalgbqid4h58jxivqdgyyxovepbvqmbl7wq"
-)
-
-func TestCommerceJS_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/commodities/commodities.go b/pkg/detectors/commodities/commodities.go
deleted file mode 100644
index e68b623877a4..000000000000
--- a/pkg/detectors/commodities/commodities.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package commodities
-
-import (
- "context"
- "io"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"commodities"}) + `\b([a-zA-Z0-9]{60})\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{"commodities"}
-}
-
-// FromData will find and optionally verify Commodities secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Commodities,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client.Timeout = 5 * time.Second
- req, err := http.NewRequestWithContext(ctx, "GET", "https://commodities-api.com/api/latest?access_key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"success":true`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Commodities
-}
-
-func (s Scanner) Description() string {
- return "Commodities API keys can be used to access and modify commodity data."
-}
diff --git a/pkg/detectors/commodities/commodities_integration_test.go b/pkg/detectors/commodities/commodities_integration_test.go
deleted file mode 100644
index 0c0ef7c2dbc0..000000000000
--- a/pkg/detectors/commodities/commodities_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package commodities
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCommodities_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COMMODITIES")
- inactiveSecret := testSecrets.MustGetField("COMMODITIES_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a commodities secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Commodities,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a commodities secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Commodities,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Commodities.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Commodities.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/commodities/commodities_test.go b/pkg/detectors/commodities/commodities_test.go
deleted file mode 100644
index cb857f603246..000000000000
--- a/pkg/detectors/commodities/commodities_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package commodities
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- commodities_key: "5aJKBqkCyGCT9FIWNUTmowbzqgcm9DUCi60mHwgPQRBSt7dFahv9eY329Dn9"
- base_url: "https://api.example.com/v1/user?access_key=$commodities_key"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "5aJKBqkCyGCT9FIWNUTmowbzqgcm9DUCi60mHwgPQRBSt7dFahv9eY329Dn9"
-)
-
-func TestCommodities_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/companyhub/companyhub.go b/pkg/detectors/companyhub/companyhub.go
deleted file mode 100644
index 7178cf8e6a6a..000000000000
--- a/pkg/detectors/companyhub/companyhub.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package companyhub
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"companyhub"}) + `\b([0-9a-zA-Z]{20})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"companyhub"}) + `\b([a-zA-Z0-9$%^=-]{4,32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"companyhub"}
-}
-
-// FromData will find and optionally verify CompanyHub secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CompanyHub,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.companyhub.com/v1/me", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("%s %s", resIdMatch, resMatch))
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CompanyHub
-}
-
-func (s Scanner) Description() string {
- return "CompanyHub is a CRM tool used to manage customer relationships. CompanyHub keys can be used to access and manipulate CRM data."
-}
diff --git a/pkg/detectors/companyhub/companyhub_integration_test.go b/pkg/detectors/companyhub/companyhub_integration_test.go
deleted file mode 100644
index 877eef42e4e7..000000000000
--- a/pkg/detectors/companyhub/companyhub_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package companyhub
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCompanyHub_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COMPANYHUB_TOKEN")
- inactiveSecret := testSecrets.MustGetField("COMPANYHUB_INACTIVE")
- user := testSecrets.MustGetField("COMPANYHUB_USER")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a companyhub secret %s within companyhubuser %s", secret, user)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CompanyHub,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a companyhub secret %s within companyhubuser %s but not valid", inactiveSecret, user)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CompanyHub,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CompanyHub.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CompanyHub.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/companyhub/companyhub_test.go b/pkg/detectors/companyhub/companyhub_test.go
deleted file mode 100644
index eb2c7faf8951..000000000000
--- a/pkg/detectors/companyhub/companyhub_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package companyhub
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- in: "Header"
- companyhub_key: "A5zrYt9xY4X1Q9mG6IX6"
- companyhub_id: "xzAMMncOTR7^5d5NS6"
- base_url: "https://api.example.com/v1/user"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secrets = []string{
- "A5zrYt9xY4X1Q9mG6IX6xzAMMncOTR7^5d5NS6",
- "A5zrYt9xY4X1Q9mG6IX6A5zrYt9xY4X1Q9mG6IX6",
- }
-)
-
-func TestCompanyHub_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/confluent/confluent.go b/pkg/detectors/confluent/confluent.go
deleted file mode 100644
index 6283e8f7c227..000000000000
--- a/pkg/detectors/confluent/confluent.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package confluent
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"confluent"}) + `\b([a-zA-Z0-9]{16})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"confluent"}) + `\b([a-zA-Z0-9\+\/]{64})\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{"confluent"}
-}
-
-// FromData will find and optionally verify Confluent secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, secret := range secretMatches {
- resSecret := strings.TrimSpace(secret[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Confluent,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resSecret),
- }
-
- if verify {
- data := fmt.Sprintf("%s:%s", resMatch, resSecret)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.confluent.cloud/iam/v2/api-keys/"+resMatch, nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Confluent
-}
-
-func (s Scanner) Description() string {
- return "Confluent provides a streaming platform based on Apache Kafka to help companies harness their data in real-time. Confluent API keys can be used to access and manage Kafka clusters."
-}
diff --git a/pkg/detectors/confluent/confluent_integration_test.go b/pkg/detectors/confluent/confluent_integration_test.go
deleted file mode 100644
index 91b535decaa5..000000000000
--- a/pkg/detectors/confluent/confluent_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package confluent
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestConfluent_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CONFLUENT_TOKEN")
- key := testSecrets.MustGetField("CONFLUENT_KEY")
- inactiveSecret := testSecrets.MustGetField("CONFLUENT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a confluent secret %s within confluent %s", secret, key)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Confluent,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a confluent secret %s within confluent %s but not valid", inactiveSecret, key)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Confluent,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Confluent.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Confluent.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/confluent/confluent_test.go b/pkg/detectors/confluent/confluent_test.go
deleted file mode 100644
index 2d541a112c64..000000000000
--- a/pkg/detectors/confluent/confluent_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package confluent
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- in: "Header"
- confluent_key: "CVAJHB4RAZboV3Od"
- confluent_secret: "pIsdFuG0oJuyiir3GWqpC4pv7xpKFodCNh6PYN4XdE8EtyIwYtzEer0KtHQ8kofs"
- base_url: "https://api.example.com/v1/user?api-key=$confluent_key"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "CVAJHB4RAZboV3OdpIsdFuG0oJuyiir3GWqpC4pv7xpKFodCNh6PYN4XdE8EtyIwYtzEer0KtHQ8kofs"
-)
-
-func TestConfluent_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalaccesstoken.go b/pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalaccesstoken.go
deleted file mode 100644
index 517951928e84..000000000000
--- a/pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalaccesstoken.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package contentfulpersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
- keyPat = regexp.MustCompile(`\b(CFPAT-[a-zA-Z0-9_\-]{43})\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{"CFPAT-"}
-}
-
-// FromData will find and optionally verify ContentfulDelivery secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range keyMatches {
- keyRes := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ContentfulPersonalAccessToken,
- Raw: []byte(keyRes),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.contentful.com/organizations", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", keyRes))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ContentfulPersonalAccessToken
-}
-
-func (s Scanner) Description() string {
- return "Contentful is a content management system (CMS) that allows users to manage and deliver digital content. Contentful Personal Access Tokens can be used to access and modify this content."
-}
diff --git a/pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalaccesstoken_test.go b/pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalaccesstoken_test.go
deleted file mode 100644
index 52c2b55b54a0..000000000000
--- a/pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalaccesstoken_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package contentfulpersonalaccesstoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- contentful_access_token: "CFPAT-"
- base_url: "https://api.example.com/v1/user"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
-)
-
-func TestContentfulPersonalAccessToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - not found",
- input: validPattern,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalacesstoken_integration_test.go b/pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalacesstoken_integration_test.go
deleted file mode 100644
index f69286595b34..000000000000
--- a/pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalacesstoken_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package contentfulpersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestContentfulPersonalAccessToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CONTENTFULPERSONALACCESSTOKEN")
- inactiveSecret := testSecrets.MustGetField("CONTENTFULPERSONALACCESSTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a contentful secret %s within contentful https://api.contentful.com/organizations", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ContentfulPersonalAccessToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a contentful secret %s within but unverified", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ContentfulPersonalAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ContentfulPersonalAccessToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
-
- t.Errorf("ContentfulPersonalAccessToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/conversiontools/conversiontools.go b/pkg/detectors/conversiontools/conversiontools.go
deleted file mode 100644
index 0272ef5c863f..000000000000
--- a/pkg/detectors/conversiontools/conversiontools.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package conversiontools
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"conversiontools"}) + `\b(ey[a-zA-Z0-9_.]{157,165})\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{"conversiontools"}
-}
-
-// FromData will find and optionally verify ConversionTools secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ConversionTools,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{ "type": "convert.website_to_jpg", "options": { "url": "http://google.com", "images": "yes" }}`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.conversiontools.io/v1/tasks", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ConversionTools
-}
-
-func (s Scanner) Description() string {
- return "ConversionTools is a service used for various data conversion tasks. The API keys can be used to access and perform these tasks."
-}
diff --git a/pkg/detectors/conversiontools/conversiontools_integration_test.go b/pkg/detectors/conversiontools/conversiontools_integration_test.go
deleted file mode 100644
index ab698d0a25ff..000000000000
--- a/pkg/detectors/conversiontools/conversiontools_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package conversiontools
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestConversionTools_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CONVERSIONTOOLS")
- inactiveSecret := testSecrets.MustGetField("CONVERSIONTOOLS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a conversiontools secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ConversionTools,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a conversiontools secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ConversionTools,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ConversionTools.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ConversionTools.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/conversiontools/conversiontools_test.go b/pkg/detectors/conversiontools/conversiontools_test.go
deleted file mode 100644
index ff0893a10751..000000000000
--- a/pkg/detectors/conversiontools/conversiontools_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package conversiontools
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- conversiontools_key: "ey5g5C73ichf2TWwQQfuPNG2SW1xdTmCHFgS6zsUjRz3kkiEofoa8X7SVGjwAMkhrv5KyOFqunP29gQpKq9A4sPF_Ps4B4IkTtgUG9cgP5A5ygAkuSR2rsOC.SIDSLIy4jZiL7L8ZHyAhyR8msV7JzxlI6YsNqmmj"
- base_url: "https://api.example.com/v1/user"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "ey5g5C73ichf2TWwQQfuPNG2SW1xdTmCHFgS6zsUjRz3kkiEofoa8X7SVGjwAMkhrv5KyOFqunP29gQpKq9A4sPF_Ps4B4IkTtgUG9cgP5A5ygAkuSR2rsOC.SIDSLIy4jZiL7L8ZHyAhyR8msV7JzxlI6YsNqmmj"
-)
-
-func TestConversionTools_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/convertapi/convertapi.go b/pkg/detectors/convertapi/convertapi.go
deleted file mode 100644
index eaa3edaf16d8..000000000000
--- a/pkg/detectors/convertapi/convertapi.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package convertapi
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"convertapi"}) + `\b(secret_[0-9a-zA-Z]{16})\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{"convertapi"}
-}
-
-// FromData will find and optionally verify ConvertApi secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ConvertApi,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://v2.convertapi.com/user?auth=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ConvertApi
-}
-
-func (s Scanner) Description() string {
- return "ConvertAPI is a service that provides file conversion capabilities via API. ConvertAPI keys can be used to access and perform file conversions."
-}
diff --git a/pkg/detectors/convertapi/convertapi_integration_test.go b/pkg/detectors/convertapi/convertapi_integration_test.go
deleted file mode 100644
index 89acb1db5823..000000000000
--- a/pkg/detectors/convertapi/convertapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package convertapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestConvertApi_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CONVERTAPI")
- inactiveSecret := testSecrets.MustGetField("CONVERTAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a convertapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ConvertApi,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a convertapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ConvertApi,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ConvertApi.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ConvertApi.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/convertapi/convertapi_test.go b/pkg/detectors/convertapi/convertapi_test.go
deleted file mode 100644
index 1e723da4c1ac..000000000000
--- a/pkg/detectors/convertapi/convertapi_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package convertapi
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- convertapi_key: "secret_H9ZGTfAERfN5W0AX"
- base_url: "https://api.example.com/v1/user?auth=$convertapi_key"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "secret_H9ZGTfAERfN5W0AX"
-)
-
-func TestConvertAPI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/convertkit/convertkit.go b/pkg/detectors/convertkit/convertkit.go
deleted file mode 100644
index 73d41bf38cda..000000000000
--- a/pkg/detectors/convertkit/convertkit.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package convertkit
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"convertkit"}) + `\b([a-z0-9A-Z_]{22})\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{"convertkit"}
-}
-
-// FromData will find and optionally verify Convertkit secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Convertkit,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.convertkit.com/v3/forms?api_key="+resMatch, nil)
- if err != nil {
- continue
- }
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Convertkit
-}
-
-func (s Scanner) Description() string {
- return "Convertkit is an email marketing service provider. API keys can be used to access and manage email marketing campaigns and subscriber data."
-}
diff --git a/pkg/detectors/convertkit/convertkit_integration_test.go b/pkg/detectors/convertkit/convertkit_integration_test.go
deleted file mode 100644
index 2b773d28c04e..000000000000
--- a/pkg/detectors/convertkit/convertkit_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package convertkit
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestConvertkit_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CONVERTKIT_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CONVERTKIT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a convertkit secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Convertkit,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a convertkit secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Convertkit,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Convertkit.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Convertkit.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/convertkit/convertkit_test.go b/pkg/detectors/convertkit/convertkit_test.go
deleted file mode 100644
index 5909f9f5b04e..000000000000
--- a/pkg/detectors/convertkit/convertkit_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package convertkit
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- convertkit_key: "hfCnuVcYOgiRjlDEmAoRbN"
- base_url: "https://api.example.com/v1/forms?api-key=$convertapi_key"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "hfCnuVcYOgiRjlDEmAoRbN"
-)
-
-func TestConvertKit_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/convier/convier.go b/pkg/detectors/convier/convier.go
deleted file mode 100644
index e8dd1517d5fa..000000000000
--- a/pkg/detectors/convier/convier.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package convier
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"convier"}) + `\b([0-9]{2}\|[a-zA-Z0-9]{40})\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{"convier"}
-}
-
-// FromData will find and optionally verify Convier secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Convier,
- Raw: []byte(resMatch),
- }
-
- if verify {
- timeout := 10 * time.Second
- client.Timeout = timeout
- req, err := http.NewRequestWithContext(ctx, "POST", "https://convier.me/api/event", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"error":false`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Convier
-}
-
-func (s Scanner) Description() string {
- return "Convier is a service for managing and verifying event data. Convier keys can be used to interact with the Convier API to manage event data."
-}
diff --git a/pkg/detectors/convier/convier_integration_test.go b/pkg/detectors/convier/convier_integration_test.go
deleted file mode 100644
index 190274a00bf4..000000000000
--- a/pkg/detectors/convier/convier_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package convier
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestConvier_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CONVIER")
- inactiveSecret := testSecrets.MustGetField("CONVIER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a convier secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Convier,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a convier secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Convier,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Convier.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Convier.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/convier/convier_test.go b/pkg/detectors/convier/convier_test.go
deleted file mode 100644
index 86d1d76b3ad6..000000000000
--- a/pkg/detectors/convier/convier_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package convier
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- convier_key: "49|07KJBwfPzF2ESyNui5yBw9OVB6eWj0iXkssEKC7b"
- base_url: "https://api.example.com/v1/user"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "49|07KJBwfPzF2ESyNui5yBw9OVB6eWj0iXkssEKC7b"
-)
-
-func TestConvier_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/copper/copper.go b/pkg/detectors/copper/copper.go
deleted file mode 100644
index 85a53e62568a..000000000000
--- a/pkg/detectors/copper/copper.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package copper
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"copper"}) + `\b([a-z0-9]{32})\b`)
- idPat = regexp.MustCompile(`\b([a-z0-9]{4,25}@[a-zA-Z0-9]{2,12}.[a-zA-Z0-9]{2,6})\b`)
-)
-
-type UserApiResponse struct {
- Id int `json:"id"`
- Email string `json:"email"`
-}
-
-// 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{"copper"}
-}
-
-// FromData will find and optionally verify Copper secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Copper,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyCopper(ctx, client, resIdMatch, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
-
- }
-
- }
-
- return results, nil
-}
-
-func verifyCopper(ctx context.Context, client *http.Client, email, apiKey string) (bool, error) {
- req, err := http.NewRequestWithContext(
- ctx,
- http.MethodGet,
- "https://api.copper.com/developer_api/v1/users/me",
- http.NoBody,
- )
- if err != nil {
- return false, err
- }
- req.Header.Add("X-PW-AccessToken", apiKey)
- req.Header.Add("X-PW-Application", "developer_api")
- req.Header.Add("X-PW-UserEmail", email)
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- respBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
-
- var respBody UserApiResponse
- if err := json.Unmarshal(respBytes, &respBody); err != nil {
- return false, err
- }
-
- // strict verification with email in credentials
- if respBody.Email == email {
- return true, nil
- }
-
- return false, fmt.Errorf("email mismatch in verification response")
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code :%d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Copper
-}
-
-func (s Scanner) Description() string {
- return "Copper is a CRM platform that helps businesses manage their relationships with customers and leads. Copper API keys can be used to access and modify customer data and interactions."
-}
diff --git a/pkg/detectors/copper/copper_integration_test.go b/pkg/detectors/copper/copper_integration_test.go
deleted file mode 100644
index 070a1aac77b6..000000000000
--- a/pkg/detectors/copper/copper_integration_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package copper
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCopper_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COOPER_TOKEN")
- inactiveSecret := testSecrets.MustGetField("COOPER_INACTIVE")
- id := testSecrets.MustGetField("COOPER_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a copper secret %s within copperid %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Copper,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a copper secret %s within copperid %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Copper,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Copper.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].RawV2 = nil
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "primarySecret")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Abstract.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/copper/copper_test.go b/pkg/detectors/copper/copper_test.go
deleted file mode 100644
index e6f9944efb13..000000000000
--- a/pkg/detectors/copper/copper_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package copper
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- in: "Header"
- copper_email: "s0ovh@P8I~p3"
- copper_token: "noqs39jzqaegbam2k6mai9ov1uwsl21y"
- base_url: "https://api.example.com/v1/user"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "noqs39jzqaegbam2k6mai9ov1uwsl21ys0ovh@P8I~p3"
-)
-
-func TestCopper_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/couchbase/couchbase.go b/pkg/detectors/couchbase/couchbase.go
deleted file mode 100644
index dda7d1430a56..000000000000
--- a/pkg/detectors/couchbase/couchbase.go
+++ /dev/null
@@ -1,147 +0,0 @@
-package couchbase
-
-import (
- "context"
- "fmt"
- "time"
- "unicode"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/couchbase/gocb/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- connectionStringPat = regexp.MustCompile(`\b(cb\.[a-z0-9]+\.cloud\.couchbase\.com)\b`)
- usernamePat = common.UsernameRegexCheck(`?()/\+=\s\n`)
- passwordPat = common.PasswordRegexCheck(`^<>;.*&|£\n\s`)
-)
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Couchbase
-}
-
-func (s Scanner) Description() string {
- return "Couchbase is a distributed NoSQL cloud database. Couchbase credentials can be used to access and modify data within the Couchbase database."
-}
-
-// 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{"couchbase://", "couchbases://"}
-}
-
-// FromData will find and optionally verify Couchbase secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueConnStrings, uniqueUsernames, uniquePasswords = make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})
-
- for _, match := range connectionStringPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueConnStrings["couchbases://"+match[1]] = struct{}{}
- }
-
- for _, match := range usernamePat.Matches(data) {
- uniqueUsernames[match] = struct{}{}
- }
-
- for _, match := range passwordPat.Matches(data) {
- uniquePasswords[match] = struct{}{}
- }
-
- for connString := range uniqueConnStrings {
- for username := range uniqueUsernames {
- for password := range uniquePasswords {
- if !isValidCouchbasePassword(password) {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Couchbase,
- Raw: fmt.Appendf([]byte(""), "%s:%s@%s", username, password, connString),
- }
-
- if verify {
- isVerified, verificationErr := verifyCouchBase(username, password, connString)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- s1.SetPrimarySecretValue(connString)
- }
-
- results = append(results, s1)
- }
- }
- }
- return results, nil
-}
-
-func verifyCouchBase(username, password, connString string) (bool, error) {
- options := gocb.ClusterOptions{
- Authenticator: gocb.PasswordAuthenticator{
- Username: username,
- Password: password,
- },
- }
-
- // Sets a pre-configured profile called "wan-development" to help avoid latency issues
- // when accessing Capella from a different Wide Area Network
- // or Availability Zone (e.g. your laptop).
- if err := options.ApplyProfile(gocb.ClusterConfigProfileWanDevelopment); err != nil {
- return false, err
- }
-
- // Initialize the Connection
- cluster, err := gocb.Connect(connString, options)
- if err != nil {
- return false, err
- }
-
- // We'll ping the KV nodes in our cluster.
- pings, err := cluster.Ping(&gocb.PingOptions{
- Timeout: time.Second * 5,
- })
-
- if err != nil {
- return false, err
- }
-
- for _, ping := range pings.Services {
- for _, pingEndpoint := range ping {
- if pingEndpoint.State == gocb.PingStateOk {
- return true, nil
- }
- }
- }
-
- return false, nil
-}
-
-func isValidCouchbasePassword(password string) bool {
- var hasLower, hasUpper, hasNumber, hasSpecialChar bool
-
- for _, r := range password {
- switch {
- case unicode.IsLower(r):
- hasLower = true
- case unicode.IsUpper(r):
- hasUpper = true
- case unicode.IsNumber(r):
- hasNumber = true
- case unicode.IsPunct(r), unicode.IsSymbol(r):
- hasSpecialChar = true
- }
- }
-
- return hasLower && hasUpper && hasNumber && hasSpecialChar
-}
diff --git a/pkg/detectors/couchbase/couchbase_integration_test.go b/pkg/detectors/couchbase/couchbase_integration_test.go
deleted file mode 100644
index 61f7f3eabfd2..000000000000
--- a/pkg/detectors/couchbase/couchbase_integration_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package couchbase
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCouchbase_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- endpoint := testSecrets.MustGetField("COUCHBASE_ENDPOINT")
- username := testSecrets.MustGetField("COUCHBASE_USERNAME")
- password := testSecrets.MustGetField("COUCHBASE_PASSWORD")
- inactiveSecret := testSecrets.MustGetField("COUCHBASE_INACTIVE_PASSWORD")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("db uri: %s \n username = %s \n password = %s", endpoint, username, password)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Couchbase,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("db uri: %s \n username = %s \n password = %s", endpoint, username, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Couchbase,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
-
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Couchbase.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])
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "primarySecret")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Couchbase.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/couchbase/couchbase_test.go b/pkg/detectors/couchbase/couchbase_test.go
deleted file mode 100644
index 267f954376fd..000000000000
--- a/pkg/detectors/couchbase/couchbase_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package couchbase
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestCouchBase_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Password"
- in: "Configuration"
- couchbase_domain: "couchbases://cb.testing.cloud.couchbase.com"
- couchbase_username: "usrpS@d>p"
- couchbase_password: "passwordU+2028 rf\@V[4,L/?2}"
- base_url: "https://$couchbase_domain/v1/user"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `,
- want: []string{
- "usrpS@d>p:passwordU+2028@couchbases://cb.testing.cloud.couchbase.com",
- "$DB_USERNAME:passwordU+2028@couchbases://cb.testing.cloud.couchbase.com",
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/countrylayer/countrylayer.go b/pkg/detectors/countrylayer/countrylayer.go
deleted file mode 100644
index a6f251769246..000000000000
--- a/pkg/detectors/countrylayer/countrylayer.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package countrylayer
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"countrylayer"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"countrylayer"}
-}
-
-// FromData will find and optionally verify CountryLayer secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CountryLayer,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.countrylayer.com/v2/all?access_key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CountryLayer
-}
-
-func (s Scanner) Description() string {
- return "CountryLayer is a service that provides information about countries. CountryLayer API keys can be used to access this information."
-}
diff --git a/pkg/detectors/countrylayer/countrylayer_integration_test.go b/pkg/detectors/countrylayer/countrylayer_integration_test.go
deleted file mode 100644
index c70dcc65ada0..000000000000
--- a/pkg/detectors/countrylayer/countrylayer_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package countrylayer
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCountryLayer_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COUNTRYLAYER")
- inactiveSecret := testSecrets.MustGetField("COUNTRYLAYER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a countrylayer secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CountryLayer,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a countrylayer secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CountryLayer,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CountryLayer.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CountryLayer.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/countrylayer/countrylayer_test.go b/pkg/detectors/countrylayer/countrylayer_test.go
deleted file mode 100644
index f97fd88cda52..000000000000
--- a/pkg/detectors/countrylayer/countrylayer_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package countrylayer
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- countrylayer_key: "031eiaqplnq39py5ppsctxo6n2xj5t10"
- base_url: "https://api.example.com/v1/user?access_key=$countrylayer_key"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "031eiaqplnq39py5ppsctxo6n2xj5t10"
-)
-
-func TestCountryLayer_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/courier/courier.go b/pkg/detectors/courier/courier.go
deleted file mode 100644
index cf9edc53077b..000000000000
--- a/pkg/detectors/courier/courier.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package courier
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"courier"}) + `\b(pk\_[a-zA-Z0-9]{1,}\_[a-zA-Z0-9]{28})\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{"courier"}
-}
-
-// FromData will find and optionally verify Courier secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Courier,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.courier.com/preferences", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Courier
-}
-
-func (s Scanner) Description() string {
- return "Courier is a notification service that allows developers to send notifications through multiple channels. Courier API keys can be used to manage and send notifications."
-}
diff --git a/pkg/detectors/courier/courier_integration_test.go b/pkg/detectors/courier/courier_integration_test.go
deleted file mode 100644
index 2b6f81803575..000000000000
--- a/pkg/detectors/courier/courier_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package courier
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCourier_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COURIER")
- inactiveSecret := testSecrets.MustGetField("COURIER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a courier secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Courier,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a courier secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Courier,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Courier.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Courier.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/courier/courier_test.go b/pkg/detectors/courier/courier_test.go
deleted file mode 100644
index b7c90a97604d..000000000000
--- a/pkg/detectors/courier/courier_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package courier
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- courier_key: "pk_iHWk6NqTne0QthfSVF7uixZpa3OTYpA8hC6bIIavhluXfxz37FB_rHPqJNWh06HpNIOuokET4dfFzXS1"
- base_url: "https://api.example.com/v1/user"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "pk_iHWk6NqTne0QthfSVF7uixZpa3OTYpA8hC6bIIavhluXfxz37FB_rHPqJNWh06HpNIOuokET4dfFzXS1"
-)
-
-func TestCourier_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/coveralls/coveralls.go b/pkg/detectors/coveralls/coveralls.go
deleted file mode 100644
index 587386841c5b..000000000000
--- a/pkg/detectors/coveralls/coveralls.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package coveralls
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"coveralls"}) + `\b([a-zA-Z0-9-]{37})\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{"coveralls"}
-}
-
-// FromData will find and optionally verify Coveralls secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Coveralls,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://coveralls.io/api/repos/github/secretscanner02/scanner", nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Coveralls
-}
-
-func (s Scanner) Description() string {
- return "Coveralls is a web service to help you track your code coverage over time, and ensure that all your new code is fully covered. Coveralls tokens can be used to access and modify coverage data."
-}
diff --git a/pkg/detectors/coveralls/coveralls_integration_test.go b/pkg/detectors/coveralls/coveralls_integration_test.go
deleted file mode 100644
index 17b7299de839..000000000000
--- a/pkg/detectors/coveralls/coveralls_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package coveralls
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCoveralls_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("COVERALLS_TOKEN")
- inactiveSecret := testSecrets.MustGetField("COVERALLS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coveralls secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coveralls,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a coveralls secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Coveralls,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Coveralls.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Coveralls.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/coveralls/coveralls_test.go b/pkg/detectors/coveralls/coveralls_test.go
deleted file mode 100644
index 32e36173e23f..000000000000
--- a/pkg/detectors/coveralls/coveralls_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package coveralls
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- in: "Header"
- coveralls_token: "tPfhjkzKJyWtUdxDLYMjNDEfP7Yn9WvWb2-K3"
- base_url: "https://api.example.com/v1/user"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "tPfhjkzKJyWtUdxDLYMjNDEfP7Yn9WvWb2-K3"
-)
-
-func TestCoveralls_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/craftmypdf/craftmypdf.go b/pkg/detectors/craftmypdf/craftmypdf.go
deleted file mode 100644
index fcbe111c7304..000000000000
--- a/pkg/detectors/craftmypdf/craftmypdf.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package craftmypdf
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"craftmypdf"}) + `\b([0-9a-zA-Z]{35})\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{"craftmypdf"}
-}
-
-// FromData will find and optionally verify CraftMyPDF secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CraftMyPDF,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.craftmypdf.com/v1/get-account-info", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-API-KEY", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CraftMyPDF
-}
-
-func (s Scanner) Description() string {
- return "CraftMyPDF is a service for generating PDFs from templates and data. CraftMyPDF API keys can be used to access and manage PDF generation tasks."
-}
diff --git a/pkg/detectors/craftmypdf/craftmypdf_integration_test.go b/pkg/detectors/craftmypdf/craftmypdf_integration_test.go
deleted file mode 100644
index b2952d214408..000000000000
--- a/pkg/detectors/craftmypdf/craftmypdf_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package craftmypdf
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCraftMyPDF_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CRAFTMYPDF")
- inactiveSecret := testSecrets.MustGetField("CRAFTMYPDF_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a craftmypdf secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CraftMyPDF,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a craftmypdf secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CraftMyPDF,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CraftMyPDF.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CraftMyPDF.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/craftmypdf/craftmypdf_test.go b/pkg/detectors/craftmypdf/craftmypdf_test.go
deleted file mode 100644
index 2df3db9f50d6..000000000000
--- a/pkg/detectors/craftmypdf/craftmypdf_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package craftmypdf
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- craftmypdf_key: "GuTSS3XQdT6fx00mxudKq7oj2CsieZCGmEc"
- base_url: "https://api.example.com/v1/user"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "GuTSS3XQdT6fx00mxudKq7oj2CsieZCGmEc"
-)
-
-func TestCraftMyPDF_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/crowdin/crowdin.go b/pkg/detectors/crowdin/crowdin.go
deleted file mode 100644
index 5abb5b110d64..000000000000
--- a/pkg/detectors/crowdin/crowdin.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package crowdin
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"crowdin"}) + `\b([0-9A-Za-z]{80})\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{"crowdin"}
-}
-
-// FromData will find and optionally verify Crowdin secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Crowdin,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.crowdin.com/api/v2/storages", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Crowdin
-}
-
-func (s Scanner) Description() string {
- return "Crowdin is a cloud-based localization management platform. Crowdin API keys can be used to access and manage localization projects and resources."
-}
diff --git a/pkg/detectors/crowdin/crowdin_integration_test.go b/pkg/detectors/crowdin/crowdin_integration_test.go
deleted file mode 100644
index 10a050d8cb04..000000000000
--- a/pkg/detectors/crowdin/crowdin_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package crowdin
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCrowdin_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CROWDIN")
- inactiveSecret := testSecrets.MustGetField("CROWDIN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a crowdin secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Crowdin,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a crowdin secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Crowdin,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Crowdin.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Crowdin.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/crowdin/crowdin_test.go b/pkg/detectors/crowdin/crowdin_test.go
deleted file mode 100644
index fab9962c45a6..000000000000
--- a/pkg/detectors/crowdin/crowdin_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package crowdin
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- crowdin_token: "BiIRgdPvboWwqlhQtlnCsM041zVYCJ5yMfgltWesDiu9bv1nuRtCEPewsDL3vgRFcp2qLemaPMa8L9g7"
- base_url: "https://api.example.com/v1/user"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "BiIRgdPvboWwqlhQtlnCsM041zVYCJ5yMfgltWesDiu9bv1nuRtCEPewsDL3vgRFcp2qLemaPMa8L9g7"
-)
-
-func TestCrowDin_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/cryptocompare/cryptocompare.go b/pkg/detectors/cryptocompare/cryptocompare.go
deleted file mode 100644
index 11bb6fa991db..000000000000
--- a/pkg/detectors/cryptocompare/cryptocompare.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package cryptocompare
-
-import (
- "context"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"cryptocompare"}) + `\b([a-z-0-9]{64})\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{"cryptocompare"}
-}
-
-// FromData will find and optionally verify CryptoCompare secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CryptoCompare,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://min-api.cryptocompare.com/data/blockchain/list?api_key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- bodyString := string(bodyBytes)
- errCode := strings.Contains(bodyString, `"Response":"Success"`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if errCode {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CryptoCompare
-}
-
-func (s Scanner) Description() string {
- return "CryptoCompare is a cryptocurrency market data provider. CryptoCompare API keys can be used to access and retrieve market data."
-}
diff --git a/pkg/detectors/cryptocompare/cryptocompare_integration_test.go b/pkg/detectors/cryptocompare/cryptocompare_integration_test.go
deleted file mode 100644
index 49f069a3c993..000000000000
--- a/pkg/detectors/cryptocompare/cryptocompare_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package cryptocompare
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCryptoCompare_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CRYPTOCOMPARE")
- inactiveSecret := testSecrets.MustGetField("CRYPTOCOMPARE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cryptocompare secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CryptoCompare,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a cryptocompare secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CryptoCompare,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CryptoCompare.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CryptoCompare.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/cryptocompare/cryptocompare_test.go b/pkg/detectors/cryptocompare/cryptocompare_test.go
deleted file mode 100644
index 9f0a319d0ac8..000000000000
--- a/pkg/detectors/cryptocompare/cryptocompare_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package cryptocompare
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- cryptocompare_key: "lx8zzovs5h15zl15mj224zks2v25re59965gz0l1z4jsc0bng33a75m5pf52-bvd"
- base_url: "https://api.example.com/v1/user?api_key=$cryptocompare_key"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "lx8zzovs5h15zl15mj224zks2v25re59965gz0l1z4jsc0bng33a75m5pf52-bvd"
-)
-
-func TestCryptoCompare_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/currencycloud/currencycloud.go b/pkg/detectors/currencycloud/currencycloud.go
deleted file mode 100644
index 188bacc3c5dc..000000000000
--- a/pkg/detectors/currencycloud/currencycloud.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package currencycloud
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"currencycloud"}) + `\b([0-9a-z]{64})\b`)
- emailPat = regexp.MustCompile(common.EmailPattern)
-)
-
-// 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{"currencycloud"}
-}
-
-// FromData will find and optionally verify Currencycloud secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- uniqueEmailMatches := make(map[string]struct{})
- for _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for emailmatch := range uniqueEmailMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CurrencyCloud,
- Raw: []byte(resMatch),
- }
- environments := []string{"devapi", "api"}
- if verify {
- for _, env := range environments {
- // Get authentication token
- payload := strings.NewReader(`{"login_id":"` + emailmatch + `","api_key":"` + resMatch + `"`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://"+env+".currencycloud.com/v2/authenticate/api", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
- if strings.Contains(body, "auth_token") {
- s1.Verified = true
- s1.ExtraData = map[string]string{"environment": fmt.Sprintf("https://%s.currencycloud.com", env)}
- break
- }
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CurrencyCloud
-}
-
-func (s Scanner) Description() string {
- return "Currencycloud provides a global payments platform that allows businesses to make payments and manage currency risk. Currencycloud API keys can be used to access and manage these financial services."
-}
diff --git a/pkg/detectors/currencycloud/currencycloud_integration_test.go b/pkg/detectors/currencycloud/currencycloud_integration_test.go
deleted file mode 100644
index cddcf55b6db8..000000000000
--- a/pkg/detectors/currencycloud/currencycloud_integration_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package currencycloud
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCurrencycloud_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CURRENCYCLOUD")
- email := testSecrets.MustGetField("SCANNERS_EMAIL")
- inactiveSecret := testSecrets.MustGetField("CURRENCYCLOUD_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a currencycloud secret %s within %s", secret, email)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CurrencyCloud,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a currencycloud secret %s within %s but not valid", inactiveSecret, email)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CurrencyCloud,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Currencycloud.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].ExtraData = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Currencycloud.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/currencycloud/currencycloud_test.go b/pkg/detectors/currencycloud/currencycloud_test.go
deleted file mode 100644
index 29641af47874..000000000000
--- a/pkg/detectors/currencycloud/currencycloud_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package currencycloud
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b / testuser1005@example.com"
- invalidPattern = "abcD123efg456HIJklmn789OPQ_rstUVWxYZ-012/testing@go"
-)
-
-func TestCurrencyCloud_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: fmt.Sprintf("currencycloud: %s", validPattern),
- want: []string{"1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("currencycloud keyword is not close to the real key and id = %s", validPattern),
- want: nil,
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("currencycloud: %s", invalidPattern),
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 && test.want != nil {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- t.Errorf("expected %d results, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/currencyfreaks/currencyfreaks.go b/pkg/detectors/currencyfreaks/currencyfreaks.go
deleted file mode 100644
index 890ef352fa0c..000000000000
--- a/pkg/detectors/currencyfreaks/currencyfreaks.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package currencyfreaks
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"currencyfreaks"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"currencyfreaks"}
-}
-
-// FromData will find and optionally verify Currencyfreaks secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Currencyfreaks,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.currencyfreaks.com/latest?apikey="+resMatch+"&format=xml", nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Currencyfreaks
-}
-
-func (s Scanner) Description() string {
- return "Currencyfreaks provides exchange rates and currency conversion API services. The API keys can be used to access and retrieve exchange rate data."
-}
diff --git a/pkg/detectors/currencyfreaks/currencyfreaks_integration_test.go b/pkg/detectors/currencyfreaks/currencyfreaks_integration_test.go
deleted file mode 100644
index e3daa56b1e71..000000000000
--- a/pkg/detectors/currencyfreaks/currencyfreaks_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package currencyfreaks
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCurrencyfreaks_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CURRENCYFREAKS")
- inactiveSecret := testSecrets.MustGetField("CURRENCYFREAKS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a currencyfreaks secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Currencyfreaks,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a currencyfreaks secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Currencyfreaks,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Currencyfreaks.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Currencyfreaks.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/currencyfreaks/currencyfreaks_test.go b/pkg/detectors/currencyfreaks/currencyfreaks_test.go
deleted file mode 100644
index 8349dd400901..000000000000
--- a/pkg/detectors/currencyfreaks/currencyfreaks_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package currencyfreaks
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- currencyfreaks_key: "6zlrpo4u8z4s72b2nqr54m9ehmqvwe8p"
- base_url: "https://api.example.com/v1/user?apiKey=$currencyfreaks_key"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "6zlrpo4u8z4s72b2nqr54m9ehmqvwe8p"
-)
-
-func TestCurrencyFreaks_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/currencylayer/currencylayer.go b/pkg/detectors/currencylayer/currencylayer.go
deleted file mode 100644
index 0e50615f8781..000000000000
--- a/pkg/detectors/currencylayer/currencylayer.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package currencylayer
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"currencylayer"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"currencylayer"}
-}
-
-// FromData will find and optionally verify Currencylayer secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Currencylayer,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.currencylayer.com/live?access_key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err2 := io.ReadAll(res.Body)
- if err2 == nil {
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"success": true`) || strings.Contains(bodyString, `"info":"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption."`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Currencylayer
-}
-
-func (s Scanner) Description() string {
- return "An API for converting and exchanging currencies. API keys can read currency data."
-}
diff --git a/pkg/detectors/currencylayer/currencylayer_integration_test.go b/pkg/detectors/currencylayer/currencylayer_integration_test.go
deleted file mode 100644
index 5cc7958d8583..000000000000
--- a/pkg/detectors/currencylayer/currencylayer_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package currencylayer
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCurrencylayer_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CURRENCYLAYER")
- inactiveSecret := testSecrets.MustGetField("CURRENCYLAYER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a currencylayer secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Currencylayer,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a currencylayer secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Currencylayer,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Currencylayer.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Currencylayer.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/currencylayer/currencylayer_test.go b/pkg/detectors/currencylayer/currencylayer_test.go
deleted file mode 100644
index bc8d98ea0e57..000000000000
--- a/pkg/detectors/currencylayer/currencylayer_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package currencylayer
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- currencylayer_key: "sxthwp257vpusfe4gr4d4awc794lkxvh"
- base_url: "https://api.example.com/v1/user?access_key=$currencylayer_key"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "sxthwp257vpusfe4gr4d4awc794lkxvh"
-)
-
-func TestCurrencyLayer_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/currencyscoop/currencyscoop.go b/pkg/detectors/currencyscoop/currencyscoop.go
deleted file mode 100644
index 3c6964dbb40b..000000000000
--- a/pkg/detectors/currencyscoop/currencyscoop.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package currencyscoop
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"currencyscoop"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"currencyscoop"}
-}
-
-// FromData will find and optionally verify Currencyscoop secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CurrencyScoop,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.currencyscoop.com/v1/latest?api_key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CurrencyScoop
-}
-
-func (s Scanner) Description() string {
- return "CurrencyScoop is a currency data service providing real-time and historical exchange rates. CurrencyScoop API keys can be used to access currency data."
-}
diff --git a/pkg/detectors/currencyscoop/currencyscoop_integration_test.go b/pkg/detectors/currencyscoop/currencyscoop_integration_test.go
deleted file mode 100644
index 378a11cff3ba..000000000000
--- a/pkg/detectors/currencyscoop/currencyscoop_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package currencyscoop
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCurrencyscoop_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CURRENCYSCOOP")
- inactiveSecret := testSecrets.MustGetField("CURRENCYSCOOP_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a currencyscoop secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CurrencyScoop,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a currencyscoop secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CurrencyScoop,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Currencyscoop.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Currencyscoop.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/currencyscoop/currencyscoop_test.go b/pkg/detectors/currencyscoop/currencyscoop_test.go
deleted file mode 100644
index 08eebc55bd90..000000000000
--- a/pkg/detectors/currencyscoop/currencyscoop_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package currencyscoop
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- currencyscoop_key: "70x6tezndca5dqlm5tnn7s03bm6c27jt"
- base_url: "https://api.example.com/v1/user?api_key=$currencylayer_key"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "70x6tezndca5dqlm5tnn7s03bm6c27jt"
-)
-
-func TestCurrencyScoop_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/currentsapi/currentsapi.go b/pkg/detectors/currentsapi/currentsapi.go
deleted file mode 100644
index 63e3f8181ab3..000000000000
--- a/pkg/detectors/currentsapi/currentsapi.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package currentsapi
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"currentsapi"}) + `([a-zA-Z0-9_-]{48})`)
-)
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CurrentsAPI
-}
-
-func (s Scanner) Description() string {
- return "CurrentsAPI provides access to the latest news and trends. CurrentsAPI keys can be used to authenticate requests and retrieve news data."
-}
-
-// 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{"currentsapi"}
-}
-
-// FromData will find and optionally verify CurrentsAPI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueTokens = make(map[string]struct{})
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokens[match[1]] = struct{}{}
- }
-
- for token := range uniqueTokens {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CurrentsAPI,
- Raw: []byte(token),
- }
-
- if verify {
- isVerified, verificationErr := verifyCurrentsAPI(ctx, client, token)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, token)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyCurrentsAPI(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.currentsapi.services/v1/latest-news", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", token)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/currentsapi/currentsapi_integration_test.go b/pkg/detectors/currentsapi/currentsapi_integration_test.go
deleted file mode 100644
index 63a8e8ed5e6b..000000000000
--- a/pkg/detectors/currentsapi/currentsapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package currentsapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCurrentsAPI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CURRENTSAPI")
- inactiveSecret := testSecrets.MustGetField("CURRENTSAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a currentsapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CurrentsAPI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a currentsapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CurrentsAPI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CurrentsAPI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CurrentsAPI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/currentsapi/currentsapi_test.go b/pkg/detectors/currentsapi/currentsapi_test.go
deleted file mode 100644
index 505b33f04903..000000000000
--- a/pkg/detectors/currentsapi/currentsapi_test.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package currentsapi
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestCurrentsAPI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- in: "Header"
- currentsapi_key: "P1ctBOMKKnSnc43K6z5E1IiPp0Q46BTrf62UHJBTcC2qkCGE"
- base_url: "https://api.example.com/v1/user"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `,
- want: []string{"P1ctBOMKKnSnc43K6z5E1IiPp0Q46BTrf62UHJBTcC2qkCGE"},
- },
- {
- name: "valid pattern",
- input: `
-
- GLOBAL
- {currentsapi}
- {AQAAABAAA -WE1-BwePKJJwiRN0lZ_qBe4WpZpgeAeYy281o5nImlhqaxG}
- configuration for production
- 2023-05-18T14:32:10Z
- jenkins-admin
-
- `,
- want: []string{"-WE1-BwePKJJwiRN0lZ_qBe4WpZpgeAeYy281o5nImlhqaxG"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/customerguru/customerguru.go b/pkg/detectors/customerguru/customerguru.go
deleted file mode 100644
index 94714af815cf..000000000000
--- a/pkg/detectors/customerguru/customerguru.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package customerguru
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"guru"}) + `\b([a-z0-9A-Z]{30})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"guru"}) + `\b([a-z0-9A-Z]{50})\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{"customerguru"}
-}
-
-// FromData will find and optionally verify CustomerGuru secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CustomerGuru,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://customer.guru/export/customers?api_secret="+resIdMatch+"&api_token="+resMatch, nil)
- if err != nil {
- continue
- }
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
-
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CustomerGuru
-}
-
-func (s Scanner) Description() string {
- return "CustomerGuru is a feedback platform used to collect and analyze customer feedback. API keys and secrets can be used to access and manage this feedback data."
-}
diff --git a/pkg/detectors/customerguru/customerguru_integration_test.go b/pkg/detectors/customerguru/customerguru_integration_test.go
deleted file mode 100644
index 3c16c76aa806..000000000000
--- a/pkg/detectors/customerguru/customerguru_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package customerguru
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCustomerGuru_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CUSTOMERGURU_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CUSTOMERGURU_INACTIVE")
- key := testSecrets.MustGetField("CUSTOMERGURU_KEY")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a customerguru secret %s within customergurukey %s", secret, key)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CustomerGuru,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a customerguru secret %s within customergurukey %s but not valid", inactiveSecret, key)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CustomerGuru,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CustomerGuru.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CustomerGuru.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/customerguru/customerguru_test.go b/pkg/detectors/customerguru/customerguru_test.go
deleted file mode 100644
index 960a8557f04e..000000000000
--- a/pkg/detectors/customerguru/customerguru_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package customerguru
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- guru_key: "WWj2zAK0tMkVJqc28Itfu6THQycyfT"
- guru_id: "Ic53IHpPK71wIacbCgEkIlFbw0VIMcsz6ir2i2DJ0XDRdirf2K"
- base_url: "https://api.customerguru.com/v1/user?api_secret=$guru_id&api_token=guru_key"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "WWj2zAK0tMkVJqc28Itfu6THQycyfTIc53IHpPK71wIacbCgEkIlFbw0VIMcsz6ir2i2DJ0XDRdirf2K"
-)
-
-func TestCustomerGuru_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/customerio/customerio.go b/pkg/detectors/customerio/customerio.go
deleted file mode 100644
index 743a020b1b5e..000000000000
--- a/pkg/detectors/customerio/customerio.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package customerio
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"customer"}) + `\b([a-z0-9A-Z]{20})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"customer"}) + `\b([a-z0-9A-Z]{20})\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{"customerio"}
-}
-
-// FromData will find and optionally verify CustomerIO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_CustomerIO,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- payload := strings.NewReader("name=purchase&data%5Bprice%5D=23.45&data%5Bproduct%5D=socks")
-
- data := fmt.Sprintf("%s:%s", resIdMatch, resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
-
- req, err := http.NewRequestWithContext(ctx, "POST", "https://track.customer.io/api/v1/customers/5/events", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_CustomerIO
-}
-
-func (s Scanner) Description() string {
- return "CustomerIO is a platform for sending automated emails, push notifications, and SMS messages. CustomerIO API keys can be used to interact with the CustomerIO service to manage customer data and trigger events."
-}
diff --git a/pkg/detectors/customerio/customerio_integration_test.go b/pkg/detectors/customerio/customerio_integration_test.go
deleted file mode 100644
index a28613e0433b..000000000000
--- a/pkg/detectors/customerio/customerio_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package customerio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestCustomerIO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("CUSTOMERIO_TOKEN")
- inactiveSecret := testSecrets.MustGetField("CUSTOMERIO_INACTIVE")
- id := testSecrets.MustGetField("CUSTOMERIO_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a customerio secret %s within customerid %s ", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CustomerIO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a customerio secret %s within customerid %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_CustomerIO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("CustomerIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("CustomerIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/customerio/customerio_test.go b/pkg/detectors/customerio/customerio_test.go
deleted file mode 100644
index e8cbf150b881..000000000000
--- a/pkg/detectors/customerio/customerio_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package customerio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- in: "Header"
- customerio_key: "bXQLU0kcl0A7kxCErc3L"
- customerio_id: "tM2JFc8pmKHUmkdwhmgG"
- base_url: "https://api.example.com/v1/user?access_key=$currencylayer_key"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secrets = []string{
- "bXQLU0kcl0A7kxCErc3LbXQLU0kcl0A7kxCErc3L",
- "bXQLU0kcl0A7kxCErc3LtM2JFc8pmKHUmkdwhmgG",
- "tM2JFc8pmKHUmkdwhmgGbXQLU0kcl0A7kxCErc3L",
- "tM2JFc8pmKHUmkdwhmgGtM2JFc8pmKHUmkdwhmgG",
- }
-)
-
-func TestCustomerio_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/d7network/d7network.go b/pkg/detectors/d7network/d7network.go
deleted file mode 100644
index 2756175f68c7..000000000000
--- a/pkg/detectors/d7network/d7network.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package d7network
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"d7network"}) + `\b([a-zA-Z0-9\W\S]{23}\=)`)
-)
-
-// 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{"d7network"}
-}
-
-// FromData will find and optionally verify D7Network secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_D7Network,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://rest-api.d7networks.com/secure/balance", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", "Basic "+resMatch)
- res, err := detectors.DetectorHttpClientWithNoLocalAddresses.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_D7Network
-}
-
-func (s Scanner) Description() string {
- return "D7Network provides messaging services through their API. The credentials can be used to send SMS and other types of messages via their platform."
-}
diff --git a/pkg/detectors/d7network/d7network_integration_test.go b/pkg/detectors/d7network/d7network_integration_test.go
deleted file mode 100644
index 737362aae3c6..000000000000
--- a/pkg/detectors/d7network/d7network_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package d7network
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestD7Network_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("D7NETWORK_TOKEN")
- inactiveSecret := testSecrets.MustGetField("D7NETWORK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a d7network secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_D7Network,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a d7network secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_D7Network,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("D7Network.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("D7Network.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/d7network/d7network_test.go b/pkg/detectors/d7network/d7network_test.go
deleted file mode 100644
index 41579c3c0228..000000000000
--- a/pkg/detectors/d7network/d7network_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package d7network
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- in: "Header"
- d7network_secret: "u@D7GXt)t>8d(LtH^(lvZ 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dailyco/dailyco.go b/pkg/detectors/dailyco/dailyco.go
deleted file mode 100644
index c4108b49cb2f..000000000000
--- a/pkg/detectors/dailyco/dailyco.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package dailyco
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"daily"}) + `\b([0-9a-f]{64})\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{"daily"}
-}
-
-// FromData will find and optionally verify DailyCO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DailyCO,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.daily.co/v1/rooms", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DailyCO
-}
-
-func (s Scanner) Description() string {
- return "DailyCO is a video calling service that provides APIs to create and manage video calls. The API keys can be used to access and control these video call services."
-}
diff --git a/pkg/detectors/dailyco/dailyco_integration_test.go b/pkg/detectors/dailyco/dailyco_integration_test.go
deleted file mode 100644
index 8832457eb5c4..000000000000
--- a/pkg/detectors/dailyco/dailyco_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dailyco
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDailyCO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DAILYCO")
- inactiveSecret := testSecrets.MustGetField("DAILYCO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dailyco secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DailyCO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dailyco secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DailyCO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("DailyCO.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("DailyCO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dailyco/dailyco_test.go b/pkg/detectors/dailyco/dailyco_test.go
deleted file mode 100644
index edc46cf281aa..000000000000
--- a/pkg/detectors/dailyco/dailyco_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package dailyco
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- dailyco_secret: "40842f16899170ffaf4e8ea99c68e748fac5e9ee5d675dd06fbe0c300a8f291a"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "40842f16899170ffaf4e8ea99c68e748fac5e9ee5d675dd06fbe0c300a8f291a"
-)
-
-func TestDailyCo_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dandelion/dandelion.go b/pkg/detectors/dandelion/dandelion.go
deleted file mode 100644
index 596aeff9c01c..000000000000
--- a/pkg/detectors/dandelion/dandelion.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package dandelion
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dandelion"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"dandelion"}
-}
-
-// FromData will find and optionally verify Dandelion secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Dandelion,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.dandelion.eu/datatxt/li/v1/?text=Smart&token=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dandelion
-}
-
-func (s Scanner) Description() string {
- return "Dandelion is a text analysis service. Dandelion tokens can be used to access and analyze text data using their APIs."
-}
diff --git a/pkg/detectors/dandelion/dandelion_integration_test.go b/pkg/detectors/dandelion/dandelion_integration_test.go
deleted file mode 100644
index 3f7f376312e4..000000000000
--- a/pkg/detectors/dandelion/dandelion_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dandelion
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDandelion_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DANDELION")
- inactiveSecret := testSecrets.MustGetField("DANDELION_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dandelion secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dandelion,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dandelion secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dandelion,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dandelion.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Dandelion.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dandelion/dandelion_test.go b/pkg/detectors/dandelion/dandelion_test.go
deleted file mode 100644
index 41f899252dcc..000000000000
--- a/pkg/detectors/dandelion/dandelion_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package dandelion
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- dandelion_secret: "xccl325526f9cp6qzh89qkgoklje5ds9"
- base_url: "https://api.example.com/v1/example?token=$dandelion_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "xccl325526f9cp6qzh89qkgoklje5ds9"
-)
-
-func TestDandelion_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dareboost/dareboost.go b/pkg/detectors/dareboost/dareboost.go
deleted file mode 100644
index 38c52423c07d..000000000000
--- a/pkg/detectors/dareboost/dareboost.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package dareboost
-
-import (
- "context"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dareboost"}) + `\b([0-9a-zA-Z]{60})\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{"dareboost"}
-}
-
-// FromData will find and optionally verify Dareboost secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Dareboost,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{ "token": "` + resMatch + `", "location": "Paris"}`)
-
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.dareboost.com/0.8/config", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
-
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"status":200`)
-
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dareboost
-}
-
-func (s Scanner) Description() string {
- return "Dareboost is a website performance monitoring tool. Dareboost API keys can be used to access and modify performance monitoring configurations."
-}
diff --git a/pkg/detectors/dareboost/dareboost_integration_test.go b/pkg/detectors/dareboost/dareboost_integration_test.go
deleted file mode 100644
index 356ebace0d8f..000000000000
--- a/pkg/detectors/dareboost/dareboost_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dareboost
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDareboost_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DAREBOOST")
- inactiveSecret := testSecrets.MustGetField("DAREBOOST_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dareboost secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dareboost,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dareboost secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dareboost,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dareboost.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Dareboost.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dareboost/dareboost_test.go b/pkg/detectors/dareboost/dareboost_test.go
deleted file mode 100644
index 0e0febe7e473..000000000000
--- a/pkg/detectors/dareboost/dareboost_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package dareboost
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- in: "Body"
- dareboost_secret: "fS6aBVkb0qpOje4VED8OhKqGGNdNVUuDhdBi9fTvxwIRMNK2uyd68WlPa1X5"
- body: {"payload":$dareboost_secret}
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "fS6aBVkb0qpOje4VED8OhKqGGNdNVUuDhdBi9fTvxwIRMNK2uyd68WlPa1X5"
-)
-
-func TestDareBoost_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/databox/databox.go b/pkg/detectors/databox/databox.go
deleted file mode 100644
index ab2da25ca559..000000000000
--- a/pkg/detectors/databox/databox.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package databox
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"databox"}) + common.BuildRegex(common.RegexPattern, "", 21))
-)
-
-// 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{"databox"}
-}
-
-// FromData will find and optionally verify Databox secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Databox,
- Raw: []byte(resMatch),
- }
- if verify {
- data := fmt.Sprintf("%s:", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- payload := strings.NewReader(`{
- "data":[
- {
- "$sales": 420,
- "$visitors": 123000
- }
- ]
- }`)
-
- req, err := http.NewRequestWithContext(ctx, "POST", "https://push.databox.com", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Accept", "application/vnd.databox.v2+json")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Databox
-}
-
-func (s Scanner) Description() string {
- return "Databox is a business analytics platform that pulls all your data into one place, so you can track performance and discover insights in real-time. Databox API keys can be used to access and modify data within your Databox account."
-}
diff --git a/pkg/detectors/databox/databox_integration_test.go b/pkg/detectors/databox/databox_integration_test.go
deleted file mode 100644
index 4492d4cdb412..000000000000
--- a/pkg/detectors/databox/databox_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package databox
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDatabox_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DATABOX")
- inactiveSecret := testSecrets.MustGetField("DATABOX_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a databox secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Databox,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a databox secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Databox,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Databox.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Databox.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/databox/databox_test.go b/pkg/detectors/databox/databox_test.go
deleted file mode 100644
index f798edb359fc..000000000000
--- a/pkg/detectors/databox/databox_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package databox
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- in: "Header"
- databox_secret: "arjrvgzxx20sivy4rigjs"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "arjrvgzxx20sivy4rigjs"
-)
-
-func TestDataBox_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/databrickstoken/databrickstoken.go b/pkg/detectors/databrickstoken/databrickstoken.go
deleted file mode 100644
index 676de74a893e..000000000000
--- a/pkg/detectors/databrickstoken/databrickstoken.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package databrickstoken
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- domain = regexp.MustCompile(`\b([a-z0-9-]+(?:\.[a-z0-9-]+)*\.(cloud\.databricks\.com|gcp\.databricks\.com|azuredatabricks\.net))\b`)
- keyPat = regexp.MustCompile(`\b(dapi[0-9a-f]{32}(-\d)?)\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{"databricks", "dapi"}
-}
-
-// FromData will find and optionally verify Databrickstoken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueDomains, uniqueTokens = make(map[string]struct{}), make(map[string]struct{})
- for _, match := range domain.FindAllStringSubmatch(dataStr, -1) {
- uniqueDomains[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokens[match[1]] = struct{}{}
- }
-
- for token := range uniqueTokens {
- for domain := range uniqueDomains {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DatabricksToken,
- Raw: []byte(token),
- RawV2: []byte(token + domain),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyDatabricksToken(client, domain, token)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
-
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{
- "token": token,
- "domain": domain,
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DatabricksToken
-}
-
-func (s Scanner) Description() string {
- return "Databricks is a cloud data platform. Databricks tokens can be used to authenticate and interact with Databricks services and APIs."
-}
-
-func verifyDatabricksToken(client *http.Client, domain, token string) (bool, error) {
- req, err := http.NewRequest(http.MethodGet, "https://"+domain+"/api/2.0/preview/scim/v2/Me", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/databrickstoken/databrickstoken_integration_test.go b/pkg/detectors/databrickstoken/databrickstoken_integration_test.go
deleted file mode 100644
index 9957dcb0f9a6..000000000000
--- a/pkg/detectors/databrickstoken/databrickstoken_integration_test.go
+++ /dev/null
@@ -1,163 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package databrickstoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDatabricksToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DATABRICKSTOKEN")
- inactiveSecret := testSecrets.MustGetField("DATABRICKSTOKEN_INACTIVE")
- domain := testSecrets.MustGetField("DATABRICKSTOKEN_DOMAIN")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a databrickstoken secret %s within %s", secret, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DatabricksToken,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a databrickstoken secret %s within %s but not valid", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DatabricksToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a databrickstoken secret %s within %s", secret, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DatabricksToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a databrickstoken secret %s within %s", secret, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DatabricksToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Databrickstoken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "AnalysisInfo")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("DatabricksToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/databrickstoken/databrickstoken_test.go b/pkg/detectors/databrickstoken/databrickstoken_test.go
deleted file mode 100644
index 2f18808b64cf..000000000000
--- a/pkg/detectors/databrickstoken/databrickstoken_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package databrickstoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- secret: "dapib8a799e452bf722cb28874cee50a7abf"
- domain: "nonprod-test.cloud.databricks.com"
- base_url: "https://$domain/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "dapib8a799e452bf722cb28874cee50a7abfnonprod-test.cloud.databricks.com"
-)
-
-func TestDataBrickStoken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/datadogtoken/datadogtoken.go b/pkg/detectors/datadogtoken/datadogtoken.go
deleted file mode 100644
index a96d7a7af2fe..000000000000
--- a/pkg/detectors/datadogtoken/datadogtoken.go
+++ /dev/null
@@ -1,198 +0,0 @@
-package datadogtoken
-
-import (
- "context"
- "encoding/json"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.EndpointSetter
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.EndpointCustomizer = (*Scanner)(nil)
-var _ detectors.CloudProvider = (*Scanner)(nil)
-
-func (Scanner) CloudEndpoint() string { return "https://api.datadoghq.com" }
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- appPat = regexp.MustCompile(detectors.PrefixRegex([]string{"datadog", "dd"}) + `\b([a-zA-Z-0-9]{40})\b`)
- apiPat = regexp.MustCompile(detectors.PrefixRegex([]string{"datadog", "dd"}) + `\b([a-zA-Z-0-9]{32})\b`)
-)
-
-type userServiceResponse struct {
- Data []*user `json:"data"`
- Included []*options `json:"included"`
-}
-
-type user struct {
- Attributes userAttributes `json:"attributes"`
-}
-
-type userAttributes struct {
- Email string `json:"email"`
- IsServiceAccount bool `json:"service_account"`
- Verified bool `json:"verified"`
- Disabled bool `json:"disabled"`
-}
-
-type options struct {
- Type string `json:"type"`
- Attributes optionAttribute `json:"attributes"`
-}
-
-type optionAttribute struct {
- Url string `json:"url"`
- Name string `json:"name"`
- Disabled bool `json:"disabled"`
-}
-
-func setUserEmails(data []*user, s1 *detectors.Result) {
- var emails []string
- for _, user := range data {
- // filter out non verified emails, disabled emails, service accounts
- if user.Attributes.Verified && !user.Attributes.Disabled && !user.Attributes.IsServiceAccount {
- emails = append(emails, user.Attributes.Email)
- }
- }
-
- if len(emails) == 0 && len(data) > 0 {
- emails = append(emails, data[0].Attributes.Email)
- }
-
- s1.ExtraData["user_emails"] = strings.Join(emails, ", ")
-}
-
-func setOrganizationInfo(opt []*options, s1 *detectors.Result) {
- var orgs *options
- for _, option := range opt {
- if option.Type == "orgs" && !option.Attributes.Disabled {
- orgs = option
- break
- }
- }
-
- if orgs != nil {
- s1.ExtraData["org_name"] = orgs.Attributes.Name
- s1.ExtraData["org_url"] = orgs.Attributes.Url
- }
-
-}
-
-// 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{"datadog"}
-}
-
-// FromData will find and optionally verify DatadogToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- appMatches := appPat.FindAllStringSubmatch(dataStr, -1)
- apiMatches := apiPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, apiMatch := range apiMatches {
- resApiMatch := strings.TrimSpace(apiMatch[1])
- appIncluded := false
- for _, appMatch := range appMatches {
- resAppMatch := strings.TrimSpace(appMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DatadogToken,
- Raw: []byte(resAppMatch),
- RawV2: []byte(resAppMatch + resApiMatch),
- ExtraData: map[string]string{
- "Type": "Application+APIKey",
- },
- }
-
- if verify {
- for _, baseURL := range s.Endpoints() {
- req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/v2/users", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("DD-API-KEY", resApiMatch)
- req.Header.Add("DD-APPLICATION-KEY", resAppMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- s1.AnalysisInfo = map[string]string{"apiKey": resApiMatch, "appKey": resAppMatch}
- var serviceResponse userServiceResponse
- if err := json.NewDecoder(res.Body).Decode(&serviceResponse); err == nil {
- // setup emails
- if len(serviceResponse.Data) > 0 {
- setUserEmails(serviceResponse.Data, &s1)
- }
- // setup organizations
- if len(serviceResponse.Included) > 0 {
- setOrganizationInfo(serviceResponse.Included, &s1)
- }
- }
- }
- }
- }
- }
- appIncluded = true
- results = append(results, s1)
- }
-
- if !appIncluded {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DatadogToken,
- Raw: []byte(resApiMatch),
- RawV2: []byte(resApiMatch),
- ExtraData: map[string]string{
- "Type": "APIKeyOnly",
- },
- }
-
- if verify {
- for _, baseURL := range s.Endpoints() {
- req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/v1/validate", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("DD-API-KEY", resApiMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- s1.AnalysisInfo = map[string]string{"apiKey": resApiMatch}
- }
- }
- }
- }
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DatadogToken
-}
-
-func (s Scanner) Description() string {
- return "Datadog is a monitoring and security platform for cloud applications. Datadog API and Application keys can be used to access and manage data and configurations within Datadog."
-}
diff --git a/pkg/detectors/datadogtoken/datadogtoken_integration_test.go b/pkg/detectors/datadogtoken/datadogtoken_integration_test.go
deleted file mode 100644
index 470a33ba8857..000000000000
--- a/pkg/detectors/datadogtoken/datadogtoken_integration_test.go
+++ /dev/null
@@ -1,153 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package datadogtoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDatadogToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- apiKey := testSecrets.MustGetField("DATADOGTOKEN_TOKEN")
- appKey := testSecrets.MustGetField("DATADOGTOKEN_APPKEY")
- inactiveAppKey := testSecrets.MustGetField("DATADOGTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a datadogtoken secret %s within datadog %s", appKey, apiKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DatadogToken,
- Verified: true,
- ExtraData: map[string]string{
- "Type": "Application+APIKey",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a datadogtoken secret %s within but datadog %s not valid", inactiveAppKey, apiKey)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DatadogToken,
- Verified: false,
- ExtraData: map[string]string{
- "Type": "Application+APIKey",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "api key found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a datadogtoken secret %s", apiKey)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DatadogToken,
- Verified: true,
- ExtraData: map[string]string{
- "Type": "APIKeyOnly",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
-
- // use default cloud endpoint
- s.UseCloudEndpoint(true)
- s.SetCloudEndpoint(s.CloudEndpoint())
-
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("DatadogToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].RawV2 = nil
- delete(got[i].ExtraData, "user_emails")
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("DatadogToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/datadogtoken/datadogtoken_test.go b/pkg/detectors/datadogtoken/datadogtoken_test.go
deleted file mode 100644
index 30ff97076a95..000000000000
--- a/pkg/detectors/datadogtoken/datadogtoken_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package datadogtoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Datadog Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- dd_api_secret: "FKNwdbyfYTmGUm5DK3yHEuK-BBQf0fVG"
- dd_app: "iHxNanzZ8vjrmbjXK7NJLrwpGw2czdSh90PKH6VL"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "iHxNanzZ8vjrmbjXK7NJLrwpGw2czdSh90PKH6VLFKNwdbyfYTmGUm5DK3yHEuK-BBQf0fVG"
-)
-
-func TestDataDogToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/datagov/datagov.go b/pkg/detectors/datagov/datagov.go
deleted file mode 100644
index 2c7e7e1714e4..000000000000
--- a/pkg/detectors/datagov/datagov.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package datagov
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"data.gov"}) + `\b([a-zA-Z0-9]{40})\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{"data.gov"}
-}
-
-// FromData will find and optionally verify DataGov secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DataGov,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.ers.usda.gov/data/arms/state?api_key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DataGov
-}
-
-func (s Scanner) Description() string {
- return "Data.gov provides access to datasets generated by the U.S. government. The API key can be used to access and retrieve data from these datasets."
-}
diff --git a/pkg/detectors/datagov/datagov_integration_test.go b/pkg/detectors/datagov/datagov_integration_test.go
deleted file mode 100644
index 41903fd76b2a..000000000000
--- a/pkg/detectors/datagov/datagov_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package datagov
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDataGov_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DATAGOV")
- inactiveSecret := testSecrets.MustGetField("DATAGOV_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a data.gov secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DataGov,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a data.gov secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DataGov,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("DataGov.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("DataGov.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/datagov/datagov_test.go b/pkg/detectors/datagov/datagov_test.go
deleted file mode 100644
index 819c9561e427..000000000000
--- a/pkg/detectors/datagov/datagov_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package datagov
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- data.gov_secret: "Ge4R2TmPk1R6NPsXYu0ceRnmawtYfnVeiZ4zztB8"
- base_url: "https://api.example.com/v1/example?api_key=$data.gov_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "Ge4R2TmPk1R6NPsXYu0ceRnmawtYfnVeiZ4zztB8"
-)
-
-func TestDataGov_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/debounce/debounce.go b/pkg/detectors/debounce/debounce.go
deleted file mode 100644
index 14d43fc92819..000000000000
--- a/pkg/detectors/debounce/debounce.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package debounce
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"debounce"}) + `\b([a-zA-Z0-9]{13})\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{"debounce"}
-}
-
-// FromData will find and optionally verify Debounce secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Debounce,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.debounce.io/v1/?api="+resMatch+"&email=some@gmail.com", nil)
- if err != nil {
- continue
- }
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Debounce
-}
-
-func (s Scanner) Description() string {
- return "Debounce is an email validation service that helps in reducing bounce rates by verifying email addresses. Debounce API keys can be used to access and validate email addresses."
-}
diff --git a/pkg/detectors/debounce/debounce_integration_test.go b/pkg/detectors/debounce/debounce_integration_test.go
deleted file mode 100644
index 4d9cc1a3e6bf..000000000000
--- a/pkg/detectors/debounce/debounce_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package debounce
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDebounce_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DEBOUNCE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("DEBOUNCE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a debounce secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Debounce,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a debounce secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Debounce,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Debounce.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Debounce.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/debounce/debounce_test.go b/pkg/detectors/debounce/debounce_test.go
deleted file mode 100644
index 314972dee8c6..000000000000
--- a/pkg/detectors/debounce/debounce_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package debounce
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- debounce_secret: "OTM0Bp42sFTRB"
- base_url: "https://api.example.com/v1/example?api=$debounce_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "OTM0Bp42sFTRB"
-)
-
-func TestDebounce_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/deepai/deepai.go b/pkg/detectors/deepai/deepai.go
deleted file mode 100644
index 00ebdc6270ee..000000000000
--- a/pkg/detectors/deepai/deepai.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package deepai
-
-import (
- "bytes"
- "context"
- "io"
- "mime/multipart"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"deepai"}) + `\b([a-z0-9-]{36})\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{"deepai"}
-}
-
-// FromData will find and optionally verify DeepAI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DeepAI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- body := &bytes.Buffer{}
- writer := multipart.NewWriter(body)
- fw, err := writer.CreateFormField("text")
- if err != nil {
- continue
- }
- _, err = io.Copy(fw, strings.NewReader("test"))
- if err != nil {
- continue
- }
- writer.Close()
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.deepai.org/api/text-tagging", bytes.NewReader(body.Bytes()))
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", writer.FormDataContentType())
- req.Header.Add("api-key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DeepAI
-}
-
-func (s Scanner) Description() string {
- return "DeepAI is an AI service provider offering various machine learning APIs. DeepAI API keys can be used to access and utilize these services."
-}
diff --git a/pkg/detectors/deepai/deepai_integration_test.go b/pkg/detectors/deepai/deepai_integration_test.go
deleted file mode 100644
index ced7b9676924..000000000000
--- a/pkg/detectors/deepai/deepai_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package deepai
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDeepAI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DEEPAI")
- inactiveSecret := testSecrets.MustGetField("DEEPAI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a deepai secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DeepAI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a deepai secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DeepAI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("DeepAI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("DeepAI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/deepai/deepai_test.go b/pkg/detectors/deepai/deepai_test.go
deleted file mode 100644
index 50020307b7a2..000000000000
--- a/pkg/detectors/deepai/deepai_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package deepai
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- deepai_secret: "ulrouaemk45y6pr8clttmjw8sucqq3skl7g9"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "ulrouaemk45y6pr8clttmjw8sucqq3skl7g9"
-)
-
-func TestDeepAI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/deepgram/deepgram.go b/pkg/detectors/deepgram/deepgram.go
deleted file mode 100644
index bfd97da42329..000000000000
--- a/pkg/detectors/deepgram/deepgram.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package deepgram
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"deepgram"}) + `\b([0-9a-z]{40})\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{"deepgram"}
-}
-
-// FromData will find and optionally verify Deepgram secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Deepgram,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.deepgram.com/v1/projects", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Deepgram
-}
-
-func (s Scanner) Description() string {
- return "Deepgram is an automatic speech recognition (ASR) service. Deepgram API keys can be used to access and utilize Deepgram's ASR capabilities."
-}
diff --git a/pkg/detectors/deepgram/deepgram_integration_test.go b/pkg/detectors/deepgram/deepgram_integration_test.go
deleted file mode 100644
index 0a8d271a0c94..000000000000
--- a/pkg/detectors/deepgram/deepgram_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package deepgram
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDeepgram_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DEEPGRAM")
- inactiveSecret := testSecrets.MustGetField("DEEPGRAM_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a deepgram secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Deepgram,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a deepgram secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Deepgram,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Deepgram.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Deepgram.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/deepgram/deepgram_test.go b/pkg/detectors/deepgram/deepgram_test.go
deleted file mode 100644
index 62c764fa59e6..000000000000
--- a/pkg/detectors/deepgram/deepgram_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package deepgram
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Token"
- in: "Header"
- deepgram_secret: "4y7fjndvwi8bydxfwe0zppeef9n6j44kpizq3zr4"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "4y7fjndvwi8bydxfwe0zppeef9n6j44kpizq3zr4"
-)
-
-func TestDeepGram_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/deepseek/deepseek.go b/pkg/detectors/deepseek/deepseek.go
deleted file mode 100644
index 15bf825ace21..000000000000
--- a/pkg/detectors/deepseek/deepseek.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package deepseek
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"deepseek"}) + `\b(sk-[a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"deepseek"}
-}
-
-// FromData will find and optionally verify DeepSeek secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for token := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DeepSeek,
- Raw: []byte(token),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- verified, extraData, verificationErr := verifyToken(ctx, client, token)
- s1.Verified = verified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyToken(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.deepseek.com/user/balance", nil)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Set("Content-Type", "application/json; charset=utf-8")
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- var resData response
- if err = json.NewDecoder(res.Body).Decode(&resData); err != nil {
- return false, nil, err
- }
-
- extraData := map[string]string{
- "is_available": fmt.Sprintf("%t", resData.IsAvailable),
- }
- return true, extraData, nil
- case http.StatusUnauthorized:
- // Invalid
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DeepSeek
-}
-
-func (s Scanner) Description() string {
- return "DeepSeek is an artificial intelligence company that develops large language models (LLMs)"
-}
-
-type response struct {
- IsAvailable bool `json:"is_available"`
-}
diff --git a/pkg/detectors/deepseek/deepseek_integration_test.go b/pkg/detectors/deepseek/deepseek_integration_test.go
deleted file mode 100644
index 72c64803bbbb..000000000000
--- a/pkg/detectors/deepseek/deepseek_integration_test.go
+++ /dev/null
@@ -1,151 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package deepseek
-
-import (
- "context"
- "fmt"
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- "testing"
- "time"
-)
-
-func TestDeepseek_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- apiKey := testSecrets.MustGetField("DEEPSEEK")
- inactiveSecret := testSecrets.MustGetField("DEEPSEEK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a deepseek secret %s within", apiKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DeepSeek,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a deepseek secret %s within but not valid", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DeepSeek,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a deepseek secret %s within", apiKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DeepSeek,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Deepseek.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- // Ignore Extra Data for comparison
- if tt.want[i].Verified == true {
- if got[i].ExtraData != nil {
- got[i].ExtraData = nil
- } else {
- t.Fatalf("no extra data")
- }
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Deepseek.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/deepseek/deepseek_test.go b/pkg/detectors/deepseek/deepseek_test.go
deleted file mode 100644
index 39f03d81876e..000000000000
--- a/pkg/detectors/deepseek/deepseek_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package deepseek
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestDeepseek_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- other.code()
- deepseek.Apikey = sk-abc123def456ghi789jkl012mno345pq
- `,
- want: []string{
- "sk-abc123def456ghi789jkl012mno345pq",
- },
- },
- {
- name: "invalid pattern",
- input: "deepseek.key = sk-abc123invalid",
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/delighted/delighted.go b/pkg/detectors/delighted/delighted.go
deleted file mode 100644
index ff00dfa31366..000000000000
--- a/pkg/detectors/delighted/delighted.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package delighted
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"delighted"}) + `\b([a-z0-9A-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"delighted"}
-}
-
-// FromData will find and optionally verify Delighted secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Delighted,
- Raw: []byte(resMatch),
- }
-
- if verify {
- data := fmt.Sprintf("%s:", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- payload := strings.NewReader(`{
- "email": "jony@appleseed.com",
- "properties": { "Purchase Experience": "Mobile App", "State": "CA" }
- }`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.delighted.com/v1/people.json", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Delighted
-}
-
-func (s Scanner) Description() string {
- return "Delighted is a customer feedback platform. Delighted API keys can be used to access and manage customer feedback data."
-}
diff --git a/pkg/detectors/delighted/delighted_integration_test.go b/pkg/detectors/delighted/delighted_integration_test.go
deleted file mode 100644
index 629f39d9e6f6..000000000000
--- a/pkg/detectors/delighted/delighted_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package delighted
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDelighted_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DELIGHTED_TOKEN")
- inactiveSecret := testSecrets.MustGetField("DELIGHTED_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a delighted secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Delighted,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a delighted secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Delighted,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Delighted.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Delighted.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/delighted/delighted_test.go b/pkg/detectors/delighted/delighted_test.go
deleted file mode 100644
index 6a49486067e7..000000000000
--- a/pkg/detectors/delighted/delighted_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package delighted
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- in: "Header"
- delighted_secret: "Vm62eJY7FFguRjYjqIdiLXUEOoRgvQ6W"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "Vm62eJY7FFguRjYjqIdiLXUEOoRgvQ6W"
-)
-
-func TestDelighted_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/demio/demio.go b/pkg/detectors/demio/demio.go
deleted file mode 100644
index ce38c691e84f..000000000000
--- a/pkg/detectors/demio/demio.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package demio
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"demio"}) + `\b([a-z0-9A-Z]{32})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"demio"}) + `\b([a-z0-9A-Z]{10,20})\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{"demio"}
-}
-
-// FromData will find and optionally verify Demio secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, idmatch := range idMatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Demio,
- Raw: []byte(resMatch),
- }
-
- if verify {
- url := fmt.Sprintf("https://my.demio.com/api/v1/ping/query?api_key=%s&api_secret=%s", resMatch, resIdMatch)
- req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Demio
-}
-
-func (s Scanner) Description() string {
- return "Demio is a webinar platform that allows users to host, promote, and analyze webinars. Demio API keys can be used to access and manage webinar data."
-}
diff --git a/pkg/detectors/demio/demio_integration_test.go b/pkg/detectors/demio/demio_integration_test.go
deleted file mode 100644
index 766783a4cf48..000000000000
--- a/pkg/detectors/demio/demio_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package demio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDemio_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DEMIO")
- inactiveSecret := testSecrets.MustGetField("DEMIO_INACTIVE")
- keySecret := testSecrets.MustGetField("DEMIO_SECRET")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a demio secret %s within demio %s", secret, keySecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Demio,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a demio secret %s within but not valid demio %s", inactiveSecret, keySecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Demio,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Demio.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Demio.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/demio/demio_test.go b/pkg/detectors/demio/demio_test.go
deleted file mode 100644
index f859304982f2..000000000000
--- a/pkg/detectors/demio/demio_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package demio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- demio_key: "KL0F0y61VeIixRmn2A4Sha3h0xiLMX7J"
- demio_secret: "PWkiVWEw7s7JjtzR"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "KL0F0y61VeIixRmn2A4Sha3h0xiLMX7J"
-)
-
-func TestDemio_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/deno/denodeploy.go b/pkg/detectors/deno/denodeploy.go
deleted file mode 100644
index 3ea648d1dc0d..000000000000
--- a/pkg/detectors/deno/denodeploy.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package denodeploy
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- tokenPat = regexp.MustCompile(`\b(dd[pw]_[a-zA-Z0-9]{36})\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{"ddp_", "ddw_"}
-}
-
-type userResponse struct {
- Login string `json:"login"`
-}
-
-// FromData will find and optionally verify DenoDeploy secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- tokenMatches := tokenPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, tokenMatch := range tokenMatches {
- token := tokenMatch[1]
-
- s1 := detectors.Result{
- DetectorType: s.Type(),
- Raw: []byte(token),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.deno.com/user", nil)
- req.Header.Set("Authorization", "Bearer "+token)
- if err != nil {
- continue
- }
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode == 200 {
- s1.Verified = true
-
- body, err := io.ReadAll(res.Body)
- if err != nil {
- s1.SetVerificationError(err, token)
- } else {
- var user userResponse
- if err := json.Unmarshal(body, &user); err != nil {
- s1.SetVerificationError(err, token)
- } else {
- s1.ExtraData = map[string]string{
- "login": user.Login,
- }
- }
- }
- } else if res.StatusCode == 401 {
- // The secret is determinately not verified (nothing to do)
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, token)
- }
- } else {
- s1.SetVerificationError(err, token)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DenoDeploy
-}
-
-func (s Scanner) Description() string {
- return "DenoDeploy is a cloud service for deploying JavaScript and TypeScript applications. DenoDeploy tokens can be used to access and manage these deployments."
-}
diff --git a/pkg/detectors/deno/denodeploy_integration_test.go b/pkg/detectors/deno/denodeploy_integration_test.go
deleted file mode 100644
index 8a2a4a4cab63..000000000000
--- a/pkg/detectors/deno/denodeploy_integration_test.go
+++ /dev/null
@@ -1,162 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package denodeploy
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDenoDeploy_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DENODEPLOY")
- inactiveSecret := testSecrets.MustGetField("DENODEPLOY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a denodeploy secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DenoDeploy,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a denodeploy secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DenoDeploy,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a denodeploy secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DenoDeploy,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a denodeploy secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DenoDeploy,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Denodeploy.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "ExtraData")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Denodeploy.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/deno/denodeploy_test.go b/pkg/detectors/deno/denodeploy_test.go
deleted file mode 100644
index 89e39f6cb517..000000000000
--- a/pkg/detectors/deno/denodeploy_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package denodeploy
-
-import (
- "context"
- "testing"
-)
-
-func TestDenoDeploy_Pattern(t *testing.T) {
- tests := []struct {
- name string
- data string
- shouldMatch bool
- match string
- }{
- // True positives
- {
- name: `valid_deployctl`,
- data: ` "tasks": {
- "d": "deployctl deploy --prod --import-map=import_map.json --project=o88 main.ts --token ddp_eg5DjUmbR5lHZ3LiN9MajMk2tA1GxL2NRdvc",
- "start": "deno run -A --unstable --watch=static/,routes/ dev.ts"
- },`,
- shouldMatch: true,
- match: `ddp_eg5DjUmbR5lHZ3LiN9MajMk2tA1GxL2NRdvc`,
- },
- {
- name: `valid_dotenv`,
- data: `DENO_KV_ACCESS_TOKEN=ddp_hn029Cl2dIN4Jb0BF0L1V9opokoPVC30ddGk`,
- shouldMatch: true,
- match: `ddp_hn029Cl2dIN4Jb0BF0L1V9opokoPVC30ddGk`,
- },
- {
- name: `valid_dotfile`,
- data: `# deno
-export DENO_INSTALL="/home/khushal/.deno"
-export PATH="$DENO_INSTALL/bin:$PATH"
-export DENO_DEPLOY_TOKEN="ddp_QLbDfRlMKpXSf3oCz20Hp8wVVxThDwlwhFbV""`,
- shouldMatch: true,
- match: `ddp_QLbDfRlMKpXSf3oCz20Hp8wVVxThDwlwhFbV`,
- },
- {
- name: `valid_webtoken`,
- data: ` // headers: { Authorization: 'Bearer ddw_ebahKKeZqiZVXOad7KJRHskLeP79Lf0OJXlj' }`,
- shouldMatch: true,
- match: `ddw_ebahKKeZqiZVXOad7KJRHskLeP79Lf0OJXlj`,
- },
-
- // False positives
- {
- name: `invalid_token1`,
- data: ` "summoner2Id": 4,
- "summonerId": "oljqJ1Ddp_LJm5s6ONPAJXIl97Bi6pcKMywYLG496a58rA",
- "summonerLevel": 146,`,
- shouldMatch: false,
- },
- {
- name: `invalid_token2`,
- data: ` "image_thumbnail_url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQFq6zzTXpXtRDdP_JbNkS58loAyCvhhZ1WWONaUkJoWbHsgwIJBw",`,
- shouldMatch: false,
- },
- {
- name: `invalid_token3`,
- data: `matplotlib/backends/_macosx.cpython-37m-darwin.so,sha256=DDw_KRE5yTUEY5iDBwBW7KvDcTkDmrIu0N18i8I3FvA,90140`,
- 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("DenoDeploy.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
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/deputy/deputy.go b/pkg/detectors/deputy/deputy.go
deleted file mode 100644
index f40775c07a5b..000000000000
--- a/pkg/detectors/deputy/deputy.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package deputy
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"deputy"}) + `\b([0-9a-z]{32})\b`)
- urlPat = regexp.MustCompile(`\b([0-9a-z]{1,}\.as\.deputy\.com)\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{"deputy"}
-}
-
-// FromData will find and optionally verify Deputy secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- urlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, urlMatch := range urlMatches {
- resURL := strings.TrimSpace(urlMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Deputy,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s/api/v1/me", resURL), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("OAuth %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Deputy
-}
-
-func (s Scanner) Description() string {
- return "Deputy is a workforce management software that provides various tools for scheduling, time tracking, and communication. Deputy API keys can be used to access and modify data within the Deputy platform."
-}
diff --git a/pkg/detectors/deputy/deputy_integration_test.go b/pkg/detectors/deputy/deputy_integration_test.go
deleted file mode 100644
index d961bab7c957..000000000000
--- a/pkg/detectors/deputy/deputy_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package deputy
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDeputy_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DEPUTY")
- url := testSecrets.MustGetField("DEPUTY_URL")
- inactiveSecret := testSecrets.MustGetField("DEPUTY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a deputy secret %s within %s", secret, url)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Deputy,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a deputy secret %s within %s but not valid", inactiveSecret, url)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Deputy,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Deputy.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Deputy.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/deputy/deputy_test.go b/pkg/detectors/deputy/deputy_test.go
deleted file mode 100644
index dc3846047c04..000000000000
--- a/pkg/detectors/deputy/deputy_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package deputy
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- deputy_secret: "puf5nguo090lkrkqxfeqj5ymm0nb26pt"
- base_url: "https://api.nonprodtest.as.deputy.com.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "puf5nguo090lkrkqxfeqj5ymm0nb26pt"
-)
-
-func TestDeputy_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/detectify/detectify.go b/pkg/detectors/detectify/detectify.go
deleted file mode 100644
index df0c1900baa9..000000000000
--- a/pkg/detectors/detectify/detectify.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package detectify
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"detectify"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"detectify"}
-}
-
-// FromData will find and optionally verify Detectify secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Detectify,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.detectify.com/rest/v2/assets/", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-Detectify-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Detectify
-}
-
-func (s Scanner) Description() string {
- return "Detectify is a web application security scanner that helps identify vulnerabilities in web applications. Detectify API keys can be used to access and manage security scans and findings."
-}
diff --git a/pkg/detectors/detectify/detectify_integration_test.go b/pkg/detectors/detectify/detectify_integration_test.go
deleted file mode 100644
index bc9872c78301..000000000000
--- a/pkg/detectors/detectify/detectify_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package detectify
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDetectify_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DETECTIFY")
- inactiveSecret := testSecrets.MustGetField("DETECTIFY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a detectify secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Detectify,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a detectify secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Detectify,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Detectify.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Detectify.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/detectify/detectify_test.go b/pkg/detectors/detectify/detectify_test.go
deleted file mode 100644
index fff938fbe2db..000000000000
--- a/pkg/detectors/detectify/detectify_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package detectify
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- detectify_secret: "eg90srff9v6cxk794kr2k56l5q5s9wx2"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "eg90srff9v6cxk794kr2k56l5q5s9wx2"
-)
-
-func TestDetectify_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/detectlanguage/detectlanguage.go b/pkg/detectors/detectlanguage/detectlanguage.go
deleted file mode 100644
index 4a9e8d248101..000000000000
--- a/pkg/detectors/detectlanguage/detectlanguage.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package detectlanguage
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"detectlanguage"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"detectlanguage"}
-}
-
-// FromData will find and optionally verify DetectLanguage secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DetectLanguage,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://ws.detectlanguage.com/0.2/user/status", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DetectLanguage
-}
-
-func (s Scanner) Description() string {
- return "DetectLanguage is a language detection API service. The API key can be used to access the language detection functionalities provided by DetectLanguage."
-}
diff --git a/pkg/detectors/detectlanguage/detectlanguage_integration_test.go b/pkg/detectors/detectlanguage/detectlanguage_integration_test.go
deleted file mode 100644
index 338a59aed1dd..000000000000
--- a/pkg/detectors/detectlanguage/detectlanguage_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package detectlanguage
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDetectLanguage_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DETECTLANGUAGE")
- inactiveSecret := testSecrets.MustGetField("DETECTLANGUAGE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a detectlanguage secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DetectLanguage,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a detectlanguage secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DetectLanguage,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("DetectLanguage.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("DetectLanguage.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/detectlanguage/detectlanguage_test.go b/pkg/detectors/detectlanguage/detectlanguage_test.go
deleted file mode 100644
index 855e1b74f6c4..000000000000
--- a/pkg/detectors/detectlanguage/detectlanguage_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package detectlanguage
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- detectlanguage_secret: "6esicmhsdpu8blum1wzr8a6bae9s507u"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "6esicmhsdpu8blum1wzr8a6bae9s507u"
-)
-
-func TestDetectLanguage_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/detectors.go b/pkg/detectors/detectors.go
deleted file mode 100644
index 86f8671eed6f..000000000000
--- a/pkg/detectors/detectors.go
+++ /dev/null
@@ -1,325 +0,0 @@
-package detectors
-
-import (
- "context"
- "crypto/rand"
- "errors"
- "math/big"
- "net/url"
- "strings"
- "unicode"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-// Detector defines an interface for scanning for and verifying secrets.
-type Detector interface {
- // FromData will scan bytes for results and optionally verify them.
- //
- // FromData can be called concurrently from multiple goroutines.
- // Any modification to the receiver or to global variables will need to to use some kind of synchronization.
- FromData(ctx context.Context, verify bool, data []byte) ([]Result, error)
-
- // Keywords are used for efficiently pre-filtering chunks using substring operations.
- // Use unique identifiers that are part of the secret if you can, or the provider name.
- //
- // When multiple keywords are provided, they are is treated as a *union* of filtering terms.
- // That is, if any of the keywords are found in a chunk, the chunk will be run through the detector.
- Keywords() []string
-
- // Type returns the DetectorType number from detectors.proto for the given detector.
- Type() detectorspb.DetectorType
-
- // Description returns a description for the result being detected
- Description() string
-}
-
-// CustomResultsCleaner is an optional interface that a detector can implement to customize how its generated results
-// are "cleaned," which is defined as removing superfluous results from those found in a given chunk. The default
-// implementation of this logic removes all unverified results if there are any verified results, and all unverified
-// results except for one otherwise, but this interface allows a detector to specify different logic. (This logic must
-// be implemented outside results generation because there are circumstances under which the engine should not execute
-// it.)
-type CustomResultsCleaner interface {
- // CleanResults removes "superfluous" results from a result set (where the definition of "superfluous" is detector-
- // specific).
- CleanResults(results []Result) []Result
- // ShouldCleanResultsIrrespectiveOfConfiguration allows a custom cleaner to instruct the engine to ignore
- // user-provided configuration that controls whether results are cleaned. (User-provided configuration is not the
- // only factor that determines whether the engine runs cleaning logic.)
- ShouldCleanResultsIrrespectiveOfConfiguration() bool
-}
-
-// Versioner is an optional interface that a detector can implement to
-// differentiate instances of the same detector type.
-type Versioner interface {
- Version() int
-}
-
-// MaxSecretSizeProvider is an optional interface that a detector can implement to
-// provide a custom max size for the secret it finds.
-type MaxSecretSizeProvider interface {
- MaxSecretSize() int64
-}
-
-// StartOffsetProvider is an optional interface that a detector can implement to
-// provide a custom start offset for the secret it finds.
-type StartOffsetProvider interface {
- StartOffset() int64
-}
-
-// MultiPartCredentialProvider is an optional interface that a detector can implement
-// to indicate its compatibility with multi-part credentials and provide the maximum
-// secret size for the credential it finds.
-type MultiPartCredentialProvider interface {
- // MaxCredentialSpan returns the maximum span or range of characters that the
- // detector should consider when searching for a multi-part credential.
- MaxCredentialSpan() int64
-}
-
-// EndpointCustomizer is an optional interface that a detector can implement to
-// support verifying against user-supplied endpoints.
-type EndpointCustomizer interface {
- SetConfiguredEndpoints(...string) error
- SetCloudEndpoint(string)
- UseCloudEndpoint(bool)
- UseFoundEndpoints(bool)
-}
-
-type CloudProvider interface {
- CloudEndpoint() string
-}
-
-type Result struct {
- // DetectorType is the type of Detector.
- DetectorType detectorspb.DetectorType
- // DetectorName is the name of the Detector. Used for custom detectors.
- DetectorName string
- // Verified indicates whether the result was verified or not.
- Verified bool
- // VerificationFromCache indicates whether this result's verification result came from the verification cache rather
- // than an actual remote request.
- VerificationFromCache bool
- // Raw contains the raw secret identifier data. Prefer IDs over secrets since it is used for deduping after hashing.
- Raw []byte
- // RawV2 contains the raw secret identifier that is a combination of both the ID and the secret.
- // This is used for secrets that are multi part and could have the same ID. Ex: AWS credentials
- RawV2 []byte
- // Redacted contains the redacted version of the raw secret identification data for display purposes.
- // A secret ID should be used if available.
- Redacted string
- ExtraData map[string]string
- StructuredData *detectorspb.StructuredData
-
- // verificationError should be populated if the verification process itself failed in a way that provides no
- // information about the verification status of the candidate secret, such as if the verification request timed out.
- verificationError error
-
- // AnalysisInfo should be set with information required for credential
- // analysis to run. The keys of the map are analyzer specific and
- // should match what is expected in the corresponding analyzer.
- AnalysisInfo map[string]string
-
- // primarySecret is used when a detector has multiple secret patterns.
- // This secret is designated to determine the line number.
- // If set, the line number will correspond to this secret.
- primarySecret struct {
- Value string
- Line int64
- }
-}
-
-// CopyVerificationInfo clones verification info (status and error) from another Result struct. This is used when
-// loading verification info from a verification cache. (A method is necessary because verification errors are not
-// exported, to prevent the accidental storage of sensitive information in them.)
-func (r *Result) CopyVerificationInfo(from *Result) {
- r.Verified = from.Verified
- r.verificationError = from.verificationError
-}
-
-// SetVerificationError is the only way to set a new verification error. Any sensitive values should be passed-in as secrets to be redacted.
-func (r *Result) SetVerificationError(err error, secrets ...string) {
- if err != nil {
- r.verificationError = redactSecrets(err, secrets...)
- }
-}
-
-// Public accessors for the fields could also be provided if needed.
-func (r *Result) VerificationError() error {
- return r.verificationError
-}
-
-// SetPrimarySecretValue set the value passed as primary secret in the result
-func (r *Result) SetPrimarySecretValue(value string) {
- if value != "" {
- r.primarySecret.Value = value
- }
-}
-
-// SetPrimarySecretLine set the passed line number as primary secret line number
-func (r *Result) SetPrimarySecretLine(line int64) {
- // line number is only set if value is set for primary secret
- if r.primarySecret.Value != "" {
- r.primarySecret.Line = line
- }
-}
-
-// GetPrimarySecretValue return primary secret match value
-func (r *Result) GetPrimarySecretValue() string {
- return r.primarySecret.Value
-}
-
-// redactSecrets replaces all instances of the given secrets with [REDACTED] in the error message.
-func redactSecrets(err error, secrets ...string) error {
- lastErr := unwrapToLast(err)
- errStr := lastErr.Error()
- for _, secret := range secrets {
- errStr = strings.ReplaceAll(errStr, secret, "[REDACTED]")
- }
- return errors.New(errStr)
-}
-
-// unwrapToLast returns the last error in the chain of errors.
-// This is added to exclude non-essential details (like URLs) for brevity and security.
-// Also helps us optimize performance in redaction and enhance log clarity.
-func unwrapToLast(err error) error {
- for {
- unwrapped := errors.Unwrap(err)
- if unwrapped == nil {
- // We've reached the last error in the chain
- return err
- }
- err = unwrapped
- }
-}
-
-type ResultWithMetadata struct {
- // IsWordlistFalsePositive indicates whether this secret was flagged as a false positive based on a wordlist check
- IsWordlistFalsePositive bool
- // SourceMetadata contains source-specific contextual information.
- SourceMetadata *source_metadatapb.MetaData
- // SourceID is the ID of the source that the API uses to map secrets to specific sources.
- SourceID sources.SourceID
- // JobID is the ID of the job that the API uses to map secrets to specific jobs.
- JobID sources.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.
- SourceType sourcespb.SourceType
- // SourceName is the name of the Source.
- SourceName string
- Result
- // DetectorDescription is the description of the Detector.
- DetectorDescription string
- // DecoderType is the type of decoder that was used to generate this result's data.
- DecoderType detectorspb.DecoderType
-}
-
-// CopyMetadata returns a detector result with included metadata from the source chunk.
-func CopyMetadata(chunk *sources.Chunk, result Result) ResultWithMetadata {
- return ResultWithMetadata{
- SourceMetadata: chunk.SourceMetadata,
- SourceID: chunk.SourceID,
- JobID: chunk.JobID,
- SecretID: chunk.SecretID,
- SourceType: chunk.SourceType,
- SourceName: chunk.SourceName,
- Result: result,
- }
-}
-
-// CleanResults returns all verified secrets, and if there are no verified secrets,
-// just one unverified secret if there are any.
-func CleanResults(results []Result) []Result {
- if len(results) == 0 {
- return results
- }
-
- var cleaned = make(map[string]Result, 0)
-
- for _, s := range results {
- if s.Verified {
- cleaned[s.Redacted] = s
- }
- }
-
- if len(cleaned) == 0 {
- return results[:1]
- }
-
- results = results[:0]
- for _, r := range cleaned {
- results = append(results, r)
- }
-
- return results
-}
-
-// PrefixRegex ensures that at least one of the given keywords is within
-// 40 characters of the capturing group that follows.
-// This can help prevent false positives.
-func PrefixRegex(keywords []string) string {
- pre := `(?i:`
- middle := strings.Join(keywords, "|")
- post := `)(?:.|[\n\r]){0,40}?`
- return pre + middle + post
-}
-
-// KeyIsRandom is a Low cost check to make sure that 'keys' include a number to reduce FPs.
-// Golang doesn't support regex lookaheads, so must be done in separate calls.
-// TODO improve checks. Shannon entropy did not work well.
-func KeyIsRandom(key string) bool {
- for _, ch := range key {
- if unicode.IsDigit(ch) {
- return true
- }
- }
-
- return false
-}
-
-func MustGetBenchmarkData() map[string][]byte {
- sizes := map[string]int{
- "xsmall": 10, // 10 bytes
- "small": 100, // 100 bytes
- "medium": 1024, // 1KB
- "large": 10 * 1024, // 10KB
- "xlarge": 100 * 1024, // 100KB
- "xxlarge": 1024 * 1024, // 1MB
- }
- data := make(map[string][]byte)
-
- for key, size := range sizes {
- // Generating a byte slice of a specific size with random data.
- content := make([]byte, size)
- for i := range size {
- randomByte, err := rand.Int(rand.Reader, big.NewInt(256))
- if err != nil {
- panic(err)
- }
- content[i] = byte(randomByte.Int64())
- }
- data[key] = content
- }
-
- return data
-}
-
-func RedactURL(u url.URL) string {
- u.User = url.UserPassword(u.User.Username(), "********")
- return strings.TrimSpace(strings.ReplaceAll(u.String(), "%2A", "*"))
-}
-
-func ParseURLAndStripPathAndParams(u string) (*url.URL, error) {
- parsedURL, err := url.Parse(u)
- if err != nil {
- return nil, err
- }
- parsedURL.Path = ""
- parsedURL.RawQuery = ""
- return parsedURL, nil
-}
diff --git a/pkg/detectors/detectors_test.go b/pkg/detectors/detectors_test.go
deleted file mode 100644
index 67c6b053cde3..000000000000
--- a/pkg/detectors/detectors_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package detectors
-
-import (
- "testing"
-
- regexp "github.com/wasilibs/go-re2"
-)
-
-func TestPrefixRegex(t *testing.T) {
- tests := []struct {
- keywords []string
- expected string
- }{
- {
- keywords: []string{"securitytrails"},
- expected: `(?i:securitytrails)(?:.|[\n\r]){0,40}?`,
- },
- {
- keywords: []string{"zipbooks"},
- expected: `(?i:zipbooks)(?:.|[\n\r]){0,40}?`,
- },
- {
- keywords: []string{"wrike"},
- expected: `(?i:wrike)(?:.|[\n\r]){0,40}?`,
- },
- }
- for _, tt := range tests {
- got := PrefixRegex(tt.keywords)
- if got != tt.expected {
- t.Errorf("PrefixRegex(%v) got: %v want: %v", tt.keywords, got, tt.expected)
- }
- }
-}
-
-func TestPrefixRegexKeywords(t *testing.T) {
- keywords := []string{"keyword1", "keyword2", "keyword3"}
-
- testCases := []struct {
- input string
- expected bool
- }{
- {"keyword1 1234c4aabceeff4444442131444aab44", true},
- {"keyword1 1234567890ABCDEF1234567890ABBBCA", false},
- {"KEYWORD1 1234567890abcdef1234567890ababcd", true},
- {"KEYWORD1 1234567890ABCDEF1234567890ABdaba", false},
- {"keyword2 1234567890abcdef1234567890abeeff", true},
- {"keyword2 1234567890ABCDEF1234567890ABadbd", false},
- {"KEYWORD2 1234567890abcdef1234567890ababca", true},
- {"KEYWORD2 1234567890ABCDEF1234567890ABBBBs", false},
- {"keyword3 1234567890abcdef1234567890abccea", true},
- {"KEYWORD3 1234567890abcdef1234567890abaabb", true},
- {"keyword4 1234567890abcdef1234567890abzzzz", false},
- {"keyword3 1234567890ABCDEF1234567890AB", false},
- {"keyword4 1234567890ABCDEF1234567890AB", false},
- }
-
- keyPat := regexp.MustCompile(PrefixRegex(keywords) + `\b([0-9a-f]{32})\b`)
-
- for _, tc := range testCases {
- match := keyPat.MatchString(tc.input)
- if match != tc.expected {
- t.Errorf("Input: %s, Expected: %v, Got: %v", tc.input, tc.expected, match)
- }
- }
-}
-
-func BenchmarkPrefixRegex(b *testing.B) {
- kws := []string{"securitytrails"}
- for i := 0; i < b.N; i++ {
- PrefixRegex(kws)
- }
-}
diff --git a/pkg/detectors/dfuse/dfuse.go b/pkg/detectors/dfuse/dfuse.go
deleted file mode 100644
index a32acea289af..000000000000
--- a/pkg/detectors/dfuse/dfuse.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package dfuse
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(web\_[0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"dfuse"}
-}
-
-// FromData will find and optionally verify Dfuse secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Dfuse,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{"api_key":"` + resMatch + `"}`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://auth.dfuse.io/v1/auth/issue", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dfuse
-}
-
-func (s Scanner) Description() string {
- return "Dfuse is a blockchain API company providing access to blockchain data and infrastructure. Dfuse API keys can be used to access and interact with blockchain data."
-}
diff --git a/pkg/detectors/dfuse/dfuse_integration_test.go b/pkg/detectors/dfuse/dfuse_integration_test.go
deleted file mode 100644
index 09fcf994d75d..000000000000
--- a/pkg/detectors/dfuse/dfuse_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dfuse
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDfuse_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DFUSE")
- inactiveSecret := testSecrets.MustGetField("DFUSE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dfuse secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dfuse,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dfuse secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dfuse,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dfuse.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Dfuse.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dfuse/dfuse_test.go b/pkg/detectors/dfuse/dfuse_test.go
deleted file mode 100644
index b01b1cf13bdf..000000000000
--- a/pkg/detectors/dfuse/dfuse_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package dfuse
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- dfuse_secret: "web_akqaeqqsrlb5bczdblzgi4g94i3yt2jb"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "web_akqaeqqsrlb5bczdblzgi4g94i3yt2jb"
-)
-
-func TestDfuse_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/diffbot/diffbot.go b/pkg/detectors/diffbot/diffbot.go
deleted file mode 100644
index 575ee79a9c15..000000000000
--- a/pkg/detectors/diffbot/diffbot.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package diffbot
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"diffbot"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"diffbot"}
-}
-
-// FromData will find and optionally verify Diffbot secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Diffbot,
- Raw: []byte(resMatch),
- }
-
- if verify {
- timeout := 10 * time.Second
- client.Timeout = timeout
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.diffbot.com/v4/account?token=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err == nil {
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"token":`) && strings.Contains(bodyString, `"planCredits":`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Diffbot
-}
-
-func (s Scanner) Description() string {
- return "Diffbot is a service that provides APIs for extracting data from web pages. Diffbot API tokens can be used to access these services and extract data from web content."
-}
diff --git a/pkg/detectors/diffbot/diffbot_integration_test.go b/pkg/detectors/diffbot/diffbot_integration_test.go
deleted file mode 100644
index f0e7d6d9c402..000000000000
--- a/pkg/detectors/diffbot/diffbot_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package diffbot
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDiffbot_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DIFFBOT")
- inactiveSecret := testSecrets.MustGetField("DIFFBOT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a diffbot secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Diffbot,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a diffbot secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Diffbot,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Diffbot.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Diffbot.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/diffbot/diffbot_test.go b/pkg/detectors/diffbot/diffbot_test.go
deleted file mode 100644
index 8225dd2cf079..000000000000
--- a/pkg/detectors/diffbot/diffbot_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package diffbot
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- diffbot_secret: "un7g0mse9r0i1m2p56832mja133vtysm"
- base_url: "https://api.example.com/v1/example?token=$diffbot_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "un7g0mse9r0i1m2p56832mja133vtysm"
-)
-
-func TestDiffBot_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/diggernaut/diggernaut.go b/pkg/detectors/diggernaut/diggernaut.go
deleted file mode 100644
index 168da9cbaa0d..000000000000
--- a/pkg/detectors/diggernaut/diggernaut.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package diggernaut
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"diggernaut"}) + `\b([0-9a-z]{40})\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{"diggernaut"}
-}
-
-// FromData will find and optionally verify Diggernaut secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Diggernaut,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.diggernaut.com/api/projects", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Diggernaut
-}
-
-func (s Scanner) Description() string {
- return "Diggernaut is a web scraping service. Diggernaut API keys can be used to access and manage scraping projects and data."
-}
diff --git a/pkg/detectors/diggernaut/diggernaut_integration_test.go b/pkg/detectors/diggernaut/diggernaut_integration_test.go
deleted file mode 100644
index 724b29d0d1fd..000000000000
--- a/pkg/detectors/diggernaut/diggernaut_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package diggernaut
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDiggernaut_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DIGGERNAUT")
- inactiveSecret := testSecrets.MustGetField("DIGGERNAUT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a diggernaut secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Diggernaut,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a diggernaut secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Diggernaut,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Diggernaut.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Diggernaut.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/diggernaut/diggernaut_test.go b/pkg/detectors/diggernaut/diggernaut_test.go
deleted file mode 100644
index a40df8e42d84..000000000000
--- a/pkg/detectors/diggernaut/diggernaut_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package diggernaut
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- diggernaut_secret: "vwrclry0t0ttuggr7gjdxarb9yb4td1618nziytp"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "vwrclry0t0ttuggr7gjdxarb9yb4td1618nziytp"
-)
-
-func TestDiggerNaut_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/digitaloceantoken/digitaloceantoken.go b/pkg/detectors/digitaloceantoken/digitaloceantoken.go
deleted file mode 100644
index 6b9f544fead0..000000000000
--- a/pkg/detectors/digitaloceantoken/digitaloceantoken.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package digitaloceantoken
-
-import (
- "context"
- "fmt"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ocean", "do"}) + `\b([A-Za-z0-9_-]{64})\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{"digitalocean"}
-}
-
-// FromData will find and optionally verify DigitalOceanToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- var uniqueTokens = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokens[matches[1]] = struct{}{}
- }
-
- for token := range uniqueTokens {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DigitalOceanToken,
- Raw: []byte(token),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- isVerified, verificationErr := verifyDigitalOceanToken(ctx, client, token)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{
- "key": token,
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyDigitalOceanToken(ctx context.Context, client *http.Client, token string) (bool, error) {
- // Ref: https://docs.digitalocean.com/reference/api/digitalocean/#tag/Account
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.digitalocean.com/v2/account", nil)
- if err != nil {
- return false, fmt.Errorf("failed to create request: %w", err)
- }
-
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- resp, err := client.Do(req)
- if err != nil {
- return false, fmt.Errorf("failed to make request: %w", err)
- }
- defer resp.Body.Close()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DigitalOceanToken
-}
-
-func (s Scanner) Description() string {
- return "DigitalOcean is a cloud infrastructure provider offering cloud services to help deploy, manage, and scale applications. DigitalOcean tokens can be used to access and manage these services."
-}
diff --git a/pkg/detectors/digitaloceantoken/digitaloceantoken_integration_test.go b/pkg/detectors/digitaloceantoken/digitaloceantoken_integration_test.go
deleted file mode 100644
index 7859ec1fc3de..000000000000
--- a/pkg/detectors/digitaloceantoken/digitaloceantoken_integration_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package digitaloceantoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDigitalOceanToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DIGITALOCEAN_PERSONAL_ACCESS_TOKEN")
- inactiveSecret := testSecrets.MustGetField("DIGITALOCEAN_PERSONAL_ACCESS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a digitaloceantoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DigitalOceanToken,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a digitaloceantoken secret %s within but unverified", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DigitalOceanToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found verifiable secret, verification failed due to unexpected API response",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a digitaloceantoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DigitalOceanToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("DigitalOceanToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "AnalysisInfo")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("DigitalOceanToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/digitaloceantoken/digitaloceantoken_test.go b/pkg/detectors/digitaloceantoken/digitaloceantoken_test.go
deleted file mode 100644
index 5e8851455748..000000000000
--- a/pkg/detectors/digitaloceantoken/digitaloceantoken_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package digitaloceantoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- digitalocean_secret: "wisN3jbppF1dA3vrcB0C40iRlNiXAvEE8ToRFHkfBQS5dt5KIq-E8_vKW7NqrJJO"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "wisN3jbppF1dA3vrcB0C40iRlNiXAvEE8ToRFHkfBQS5dt5KIq-E8_vKW7NqrJJO"
-)
-
-func TestDigitalOceanToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/digitaloceanv2/digitaloceanv2.go b/pkg/detectors/digitaloceanv2/digitaloceanv2.go
deleted file mode 100644
index caf5c7a17756..000000000000
--- a/pkg/detectors/digitaloceanv2/digitaloceanv2.go
+++ /dev/null
@@ -1,168 +0,0 @@
-package digitaloceanv2
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b((?:dop|doo|dor)_v1_[a-f0-9]{64})\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{"dop_v1_", "doo_v1_", "dor_v1_"}
-}
-
-// FromData will find and optionally verify DigitalOceanV2 secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueTokens = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokens[matches[0]] = struct{}{}
- }
-
- for token := range uniqueTokens {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DigitalOceanV2,
- Raw: []byte(token),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- // Check if the token is a refresh token or an access token
- switch {
- case strings.HasPrefix(token, "dor_v1_"):
- verified, verificationErr, newAccessToken := verifyRefreshToken(ctx, client, token)
- s1.SetVerificationError(verificationErr)
- s1.Verified = verified
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{
- "key": newAccessToken,
- }
- }
- case strings.HasPrefix(token, "doo_v1_"), strings.HasPrefix(token, "dop_v1_"):
- verified, verificationErr := verifyAccessToken(ctx, client, token)
- s1.Verified = verified
- s1.SetVerificationError(verificationErr)
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{
- "key": token,
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-// verifyRefreshToken verifies the refresh token by making a request to the DigitalOcean API.
-// If the token is valid, it returns the new access token and no error.
-// If the token is invalid/expired, it returns an empty string and no error.
-// If an error is encountered, it returns an empty string along and the error.
-func verifyRefreshToken(ctx context.Context, client *http.Client, token string) (bool, error, string) {
- // Ref: https://docs.digitalocean.com/reference/api/oauth/
-
- url := "https://cloud.digitalocean.com/v1/oauth/token?grant_type=refresh_token&refresh_token=" + token
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
- if err != nil {
- return false, fmt.Errorf("failed to create request: %w", err), ""
- }
-
- res, err := client.Do(req)
- if err != nil {
- return false, fmt.Errorf("failed to make request: %w", err), ""
- }
-
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, fmt.Errorf("failed to read response body: %w", err), ""
- }
- defer res.Body.Close()
-
- switch res.StatusCode {
- case http.StatusOK:
- var responseMap map[string]interface{}
- if err := json.Unmarshal(bodyBytes, &responseMap); err != nil {
- return false, fmt.Errorf("failed to parse response body: %w", err), ""
- }
- // Extract the access token from the response
- accessToken, exists := responseMap["access_token"].(string)
- if !exists {
- return false, fmt.Errorf("access_token not found in response: %s", string(bodyBytes)), ""
- }
- return true, nil, accessToken
- case http.StatusUnauthorized:
- return false, nil, ""
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode), ""
- }
-}
-
-// verifyAccessToken verifies the access token by making a request to the DigitalOcean API.
-// If the token is valid, it returns true and no error.
-// If the token is invalid, it returns false and no error.
-// If an error is encountered, it returns false along with the error.
-func verifyAccessToken(ctx context.Context, client *http.Client, token string) (bool, error) {
- // Ref: https://docs.digitalocean.com/reference/api/digitalocean/#tag/Account
-
- url := "https://api.digitalocean.com/v2/account"
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
- if err != nil {
- return false, fmt.Errorf("failed to create request: %w", err)
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, fmt.Errorf("failed to make request: %w", err)
- }
- defer res.Body.Close()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DigitalOceanV2
-}
-
-func (s Scanner) Description() string {
- return "DigitalOcean is a cloud service provider offering scalable compute and storage solutions. DigitalOcean API keys can be used to access and manage these resources."
-}
diff --git a/pkg/detectors/digitaloceanv2/digitaloceanv2_integration_test.go b/pkg/detectors/digitaloceanv2/digitaloceanv2_integration_test.go
deleted file mode 100644
index a1736df26f28..000000000000
--- a/pkg/detectors/digitaloceanv2/digitaloceanv2_integration_test.go
+++ /dev/null
@@ -1,141 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package digitaloceanv2
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDigitalOceanV2_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DIGITALOCEANV2")
- inactiveSecret := testSecrets.MustGetField("DIGITALOCEANV2_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a digitaloceanv2 secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DigitalOceanV2,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a digitaloceanv2 secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DigitalOceanV2,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- {
- name: "found verifiable secret, verification failed due to unexpected API response",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a digitaloceanv2 secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DigitalOceanV2,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("DigitalOceanV2.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "AnalysisInfo")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("DigitalOceanV2.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/digitaloceanv2/digitaloceanv2_test.go b/pkg/detectors/digitaloceanv2/digitaloceanv2_test.go
deleted file mode 100644
index 0aee3ca9f795..000000000000
--- a/pkg/detectors/digitaloceanv2/digitaloceanv2_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package digitaloceanv2
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- digitalocean_secret1: "doo_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d"
- digitalocean_secret2: "dop_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d"
- digitalocean_secret3: "dor_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d"
- base_url: "https://api.example.com/v1/example?refresh_token=$digitalocean_secret1"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secrets = []string{
- "doo_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d",
- "dop_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d",
- "dor_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d",
- }
-)
-
-func TestDigitalOceanV2_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/discordbottoken/discordbottoken.go b/pkg/detectors/discordbottoken/discordbottoken.go
deleted file mode 100644
index aeed56051191..000000000000
--- a/pkg/detectors/discordbottoken/discordbottoken.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package discordbottoken
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"discord"}) + `\b([0-9]{17})\b`)
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"discord"}) + `\b([A-Za-z0-9_-]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27})\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{"discord"}
-}
-
-// FromData will find and optionally verify DiscordBotToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatch := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idmatch := range idMatch {
- resId := strings.TrimSpace(idmatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DiscordBotToken,
- Redacted: resId,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resId),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://discord.com/api/v8/users/"+resId, nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bot %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DiscordBotToken
-}
-
-func (s Scanner) Description() string {
- return "Discord bot tokens are used to authenticate and control Discord bots. These tokens can be used to interact with the Discord API to perform various bot-related operations."
-}
diff --git a/pkg/detectors/discordbottoken/discordbottoken_integration_test.go b/pkg/detectors/discordbottoken/discordbottoken_integration_test.go
deleted file mode 100644
index 964c14e22bd2..000000000000
--- a/pkg/detectors/discordbottoken/discordbottoken_integration_test.go
+++ /dev/null
@@ -1,127 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package discordbottoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDiscordBotToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DISCORDBOTTOKEN_TOKEN")
- inactiveSecret := testSecrets.MustGetField("DISCORDBOTTOKEN_INACTIVE")
- idSecret := testSecrets.MustGetField("DISCORDBOTTOKEN_USERID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a discordbot secret %s within https://discord.com/api/v8/users/%s", secret, idSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DiscordBotToken,
- Redacted: idSecret,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a discordbot secret %s within https://discord.com/api/v8/users/%s but not valid", inactiveSecret, idSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DiscordBotToken,
- Redacted: idSecret,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("DiscordBotToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no raw v2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("DiscordBotToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/discordbottoken/discordbottoken_test.go b/pkg/detectors/discordbottoken/discordbottoken_test.go
deleted file mode 100644
index cd73b7db6aa9..000000000000
--- a/pkg/detectors/discordbottoken/discordbottoken_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package discordbottoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Token"
- in: "Header"
- discord_id: "17014529625858348"
- discord_secret: "oHILWmk3qakMYbqAikD9R0nJ.Vhu0LY.FK1U_2L2Of8Bm5ESbD6Cy4VKu2K"
- base_url: "https://api.example.com/v1/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "oHILWmk3qakMYbqAikD9R0nJ.Vhu0LY.FK1U_2L2Of8Bm5ESbD6Cy4VKu2K17014529625858348"
-)
-
-func TestDiscordBotToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/discordwebhook/discordwebhook.go b/pkg/detectors/discordwebhook/discordwebhook.go
deleted file mode 100644
index d06739f14b00..000000000000
--- a/pkg/detectors/discordwebhook/discordwebhook.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package discordwebhook
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`(https:\/\/discord\.com\/api\/webhooks\/[0-9]{18,19}\/[0-9a-zA-Z-]{68})`)
-)
-
-// 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{"https://discord.com/api/webhooks/"} }
-
-// FromData will find and optionally verify DiscordWebhook secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DiscordWebhook,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DiscordWebhook
-}
-
-func (s Scanner) Description() string {
- return "Discord webhooks are used to send messages to a Discord channel. They can be used to automate messages and send data updates."
-}
diff --git a/pkg/detectors/discordwebhook/discordwebhook_integration_test.go b/pkg/detectors/discordwebhook/discordwebhook_integration_test.go
deleted file mode 100644
index 50a874715429..000000000000
--- a/pkg/detectors/discordwebhook/discordwebhook_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package discordwebhook
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDiscordWebhook_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DISCORDWEBHOOK")
- inactiveSecret := testSecrets.MustGetField("DISCORDWEBHOOK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a discordwebhook secret %s within discordwebhook", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DiscordWebhook,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a discordwebhook secret %s within discordwebhook but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DiscordWebhook,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("DiscordWebhook.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("DiscordWebhook.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/discordwebhook/discordwebhook_test.go b/pkg/detectors/discordwebhook/discordwebhook_test.go
deleted file mode 100644
index 7fec8c0bdf1c..000000000000
--- a/pkg/detectors/discordwebhook/discordwebhook_test.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package discordwebhook
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- in: ""
- discord_hook: "https://discord.com/api/webhooks/144147826297622273/Rz9B09dB7cXxtldzXXfmJY0opIzgeANtGJw08vx5PXrP8BpbOeE5lZ7wx8vVcyacYkEl"
- base_url: ""
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "https://discord.com/api/webhooks/144147826297622273/Rz9B09dB7cXxtldzXXfmJY0opIzgeANtGJw08vx5PXrP8BpbOeE5lZ7wx8vVcyacYkEl"
-
- validPattern19Digits = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- in: ""
- discord_hook: "https://discord.com/api/webhooks/1369248176954937405/Q7bFGgbEMoZ-tRHuA4QHk3xTNC7nrrSmTm8IPjFvkp-ChRj4gi2C9lzvJiUcVlnE48X2"
- base_url: ""
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret19Digits = "https://discord.com/api/webhooks/1369248176954937405/Q7bFGgbEMoZ-tRHuA4QHk3xTNC7nrrSmTm8IPjFvkp-ChRj4gi2C9lzvJiUcVlnE48X2"
-)
-
-func TestDiscordWebHook_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- {
- name: "valid pattern with 19-digit ID",
- input: validPattern19Digits,
- want: []string{secret19Digits},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/disqus/disqus.go b/pkg/detectors/disqus/disqus.go
deleted file mode 100644
index 05b05edd432b..000000000000
--- a/pkg/detectors/disqus/disqus.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package disqus
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"disqus"}) + `\b([a-zA-Z0-9]{64})\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{"disqus"}
-}
-
-// FromData will find and optionally verify Disqus secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Disqus,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://disqus.com/api/3.0/trends/listThreads.json?api_key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Disqus
-}
-
-func (s Scanner) Description() string {
- return "Disqus is a networked community platform used for web comments and discussions. Disqus API keys can be used to access and manage comments and user data."
-}
diff --git a/pkg/detectors/disqus/disqus_integration_test.go b/pkg/detectors/disqus/disqus_integration_test.go
deleted file mode 100644
index 8c5aa5e45d0b..000000000000
--- a/pkg/detectors/disqus/disqus_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package disqus
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDisqus_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DISQUS")
- inactiveSecret := testSecrets.MustGetField("DISQUS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a disqus secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Disqus,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a disqus secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Disqus,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Disqus.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Disqus.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/disqus/disqus_test.go b/pkg/detectors/disqus/disqus_test.go
deleted file mode 100644
index c975fd85a0e9..000000000000
--- a/pkg/detectors/disqus/disqus_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package disqus
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- base_url: "https://api.disqus.com/v3/example?token=T7YaiuviPyYp8WyWlJ9lqQLI5oPirYMcfDYLPY7NAqxAr3872ovqq9AOVU3RcPUB"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "T7YaiuviPyYp8WyWlJ9lqQLI5oPirYMcfDYLPY7NAqxAr3872ovqq9AOVU3RcPUB"
-)
-
-func TestDisqus_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/ditto/ditto.go b/pkg/detectors/ditto/ditto.go
deleted file mode 100644
index 964e86bc285a..000000000000
--- a/pkg/detectors/ditto/ditto.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package ditto
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ditto"}) + `\b([a-z0-9]{8}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{12}\.[a-z0-9]{40})\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{"ditto"}
-}
-
-// FromData will find and optionally verify Ditto secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Ditto,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.dittowords.com/variants", nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Ditto
-}
-
-func (s Scanner) Description() string {
- return "Ditto is a service that provides API access to various word variants. Ditto API keys can be used to access this service and retrieve word variants."
-}
diff --git a/pkg/detectors/ditto/ditto_integration_test.go b/pkg/detectors/ditto/ditto_integration_test.go
deleted file mode 100644
index ee7c21b9557e..000000000000
--- a/pkg/detectors/ditto/ditto_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package ditto
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDitto_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DITTO")
- inactiveSecret := testSecrets.MustGetField("DITTO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ditto secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Ditto,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ditto secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Ditto,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Ditto.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Ditto.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/ditto/ditto_test.go b/pkg/detectors/ditto/ditto_test.go
deleted file mode 100644
index e2100f5158ed..000000000000
--- a/pkg/detectors/ditto/ditto_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package ditto
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- api_version: v1
- ditto_secret: "smtkww1b-bpux-6mds-r977-7kr1rb1q8r5o.4jwv35awjadnwzzm4u4kz8otf3lgmns2oazb8f6w"
- base_url: "https://api.ditto.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "smtkww1b-bpux-6mds-r977-7kr1rb1q8r5o.4jwv35awjadnwzzm4u4kz8otf3lgmns2oazb8f6w"
-)
-
-func TestDitto_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dnscheck/dnscheck.go b/pkg/detectors/dnscheck/dnscheck.go
deleted file mode 100644
index c2bec252da12..000000000000
--- a/pkg/detectors/dnscheck/dnscheck.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package dnscheck
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dnscheck"}) + `\b([a-z0-9A-Z]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dnscheck"}) + `\b([a-z0-9A-Z-]{36})\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{"dnscheck"}
-}
-
-// FromData will find and optionally verify Dnscheck secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Dnscheck,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.dnscheck.co/api/v1/groups/"+resIdMatch+"?api_key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dnscheck
-}
-
-func (s Scanner) Description() string {
- return "Dnscheck is a service used to monitor DNS records. The API keys can be used to access and manage DNS monitoring configurations."
-}
diff --git a/pkg/detectors/dnscheck/dnscheck_integration_test.go b/pkg/detectors/dnscheck/dnscheck_integration_test.go
deleted file mode 100644
index 374b484bcf89..000000000000
--- a/pkg/detectors/dnscheck/dnscheck_integration_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dnscheck
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDnscheck_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DNSCHECK")
- inactiveSecret := testSecrets.MustGetField("DNSCHECK_INACTIVE")
- id := testSecrets.MustGetField("DNSCHECK_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dnscheck secret %s within dnscheckid %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dnscheck,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dnscheck secret %s within dnscheckid %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dnscheck,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dnscheck.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no raw v2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Dnscheck.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dnscheck/dnscheck_test.go b/pkg/detectors/dnscheck/dnscheck_test.go
deleted file mode 100644
index af90303c4004..000000000000
--- a/pkg/detectors/dnscheck/dnscheck_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package dnscheck
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- api_version: v1
- dnscheck_secret: "GaMSE8mJT7evjXg1Tmwz0wAyrY4Yagur"
- base_url: "https://api.dnscheck.com/$api_version/groups/zDTON8dac54pwe1OaCrKhcwC9qptimIdX42K?api_key=$dnscheck_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "GaMSE8mJT7evjXg1Tmwz0wAyrY4YagurzDTON8dac54pwe1OaCrKhcwC9qptimIdX42K"
-)
-
-func TestDnsCheck_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/docker/docker_auth_config.go b/pkg/detectors/docker/docker_auth_config.go
deleted file mode 100644
index 785dd4962643..000000000000
--- a/pkg/detectors/docker/docker_auth_config.go
+++ /dev/null
@@ -1,316 +0,0 @@
-package docker
-
-import (
- "context"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strings"
-
- "github.com/go-logr/logr"
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ interface {
- detectors.Detector
- detectors.MaxSecretSizeProvider
-} = (*Scanner)(nil)
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Docker
-}
-
-func (s Scanner) Description() string {
- return "Docker credentials can be used to pull images from private registries."
-}
-
-// 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{`"auths"`, `\"auths\`}
-}
-
-func (s Scanner) MaxSecretSize() int64 {
- return 4096
-}
-
-var (
- keyPat = regexp.MustCompile(`{(?:\s|\\+[nrt])*\\*"auths\\*"(?:\s|\\+t)*:(?:\s|\\+t)*{(?:\s|\\+[nrt])*\\*"(?i:https?:\/\/)?[a-z0-9\-.:\/]+\\*"(?:\s|\\+t)*:(?:\s|\\+t)*{(?:(?:\s|\\+[nrt])*\\*"(?i:auth|email|username|password)\\*"\s*:\s*\\*".*\\*"\s*,?)+?(?:\s|\\+[nrt])*}(?:\s|\\+[nrt])*}(?:\s|\\+[nrt])*}`)
- escapedReplacer = strings.NewReplacer(
- `\n`, "",
- `\r`, "",
- `\t`, "",
- `\\`, ``,
- `\"`, `"`,
- )
-
- // Common false-positives used in examples.
- exampleRegistries = map[string]struct{}{
- "https://index.docker.io/v1/": {}, // https://github.com/moby/moby/blob/34679e568a22b4f35ff8460f3b5b7bf7089df818/cliconfig/config_test.go#L259
- "registry.hostname.com": {}, // https://github.com/openshift/machine-config-operator/blob/82011335dbdd3d4c869b959d6048a3fba7742e47/pkg/controller/build/helpers_test.go#L47
- "registry.example.com:5000": {}, // https://github.com/openshift/cluster-baremetal-operator/blob/f908020b1d46667056f21cf1d79e032c535a41fc/provisioning/baremetal_secrets_test.go#L53
- "registry2.example.com:5000": {},
- "your.private.registry.example.com": {}, // https://github.com/kubernetes/website/blob/d130f326758988553c42179c087bfeec5bf948a0/content/en/docs/tasks/configure-pod-container/pull-image-private-registry.md?plain=1#L167
- }
-)
-
-// FromData will find and optionally verify Docker secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- logCtx := logContext.AddLogger(ctx)
- logger := logCtx.Logger().WithName("docker")
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[0]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- // Remove escaped quotes and literal whitespace characters, if present.
- // It is common for auth to be escaped, however, the json package cannot unmarshal escaped JSON.
- match := escapedReplacer.Replace(match)
-
- // Unmarshal the config string.
- // Doing byte->string->byte probably isn't the most efficient.
- var auths dockerAuths
- if err := json.NewDecoder(strings.NewReader(match)).Decode(&auths); err != nil {
- logger.Error(err, "Could not parse Docker auth JSON")
- return results, err
- } else if len(auths.Auths) == 0 {
- continue
- }
-
- for registry, auth := range auths.Auths {
- // `docker.io` is a special case, Docker is hard-coded to rewrite it as `index.docker.io`.
- // https://github.com/moby/moby/blob/145a73a36c171b34c196ad780e699b154ddf47b5/registry/config_test.go#L329
- if strings.EqualFold(registry, "docker.io") {
- registry = "index.docker.io"
- }
-
- // Skip known invalid registries.
- if _, ok := exampleRegistries[registry]; ok {
- continue
- }
-
- // Skip configs with no credentials.
- // TODO: Should this be an error? What if it's a logic issue?
- username, password, b64encoded := parseBasicAuth(logger, auth)
- if username == "" && password == "" {
- logger.V(2).Info("Skipping empty credentials", "auth", auth, "username", username, "password", password)
- continue
- }
-
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_Docker,
- Raw: []byte(b64encoded),
- RawV2: []byte(`{"registry":"` + registry + `","auth":"` + b64encoded + `"}`),
- ExtraData: map[string]string{"Username": username},
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = common.SaneHttpClient()
- }
-
- isVerified, verificationErr := verifyMatch(logCtx, client, registry, username, b64encoded)
- r.Verified = isVerified
- r.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, r)
- }
- }
- return
-}
-
-func verifyMatch(ctx logContext.Context, client *http.Client, registry string, username string, basicAuth string) (bool, error) {
- // Build the registry URL path.
- var registryUrl string
- registry, _ = strings.CutSuffix(registry, "/")
- if strings.HasPrefix(registry, "http://") || strings.HasPrefix(registry, "https://") {
- registryUrl = registry + "/v2/"
- } else {
- registryUrl = "https://" + registry + "/v2/"
- }
-
- // Build the request.
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, registryUrl, nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Set("Authorization", "Basic "+basicAuth)
- req.Header.Set("Accept", "application/json")
- req.Header.Set("Content-Type", "application/json")
-
- // Send the initial request.
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- // Handle the initial response.
- switch res.StatusCode {
- case http.StatusOK:
- body, err := io.ReadAll(res.Body)
- if err != nil {
- return false, err
- }
-
- return json.Valid(body), nil
- case http.StatusUnauthorized:
- // Some registries do not support basic auth, so we must follow the `Www-Authenticate` header, if present.
- // https://distribution.github.io/distribution/spec/auth/token/
- h := res.Header.Get("Www-Authenticate")
- if h == "" {
- return false, nil
- }
-
- if !strings.HasPrefix(h, "Bearer") {
- return false, fmt.Errorf("unsupported WWW-Authenticate auth scheme: %s", h)
- }
-
- authParams, err := parseAuthenticateHeader(h)
- if err != nil {
- return false, fmt.Errorf("failed to parse registry auth header: %w", err)
- }
- realm := authParams["realm"]
- if realm == "" {
- return false, fmt.Errorf("unexpected empty realm for WWW-Authenticate header: %s", h)
- }
-
- authReq, err := http.NewRequestWithContext(ctx, http.MethodGet, realm, nil)
- if err != nil {
- return false, nil
- }
-
- authReq.Header.Set("Authorization", "Basic "+basicAuth)
- authReq.Header.Set("Accept", "application/json")
- authReq.Header.Set("Content-Type", "application/json")
-
- params := url.Values{}
- params.Add("account", username)
- params.Add("service", authParams["service"])
- authReq.URL.RawQuery = params.Encode()
-
- authRes, err := client.Do(authReq)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, authRes.Body)
- _ = authRes.Body.Close()
- }()
-
- switch authRes.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- // Auth was rejected.
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d for '%s'", authRes.StatusCode, authReq.URL.String())
- }
- default:
- err = fmt.Errorf("unexpected HTTP response status %d for '%s'", res.StatusCode, req.URL.String())
- return false, err
- }
-}
-
-type dockerAuths struct {
- Auths map[string]dockerAuth `json:"auths"`
-}
-
-type dockerAuth struct {
- Auth string `json:"auth"`
- Username string `json:"username"`
- Password string `json:"password"`
- Email string `json:"email"`
-}
-
-// parseBasicAuth handles cases where configs can have `username` and `password` but no `auth`,
-// or vice-versa.
-func parseBasicAuth(logger logr.Logger, auth dockerAuth) (string, string, string) {
- var (
- username string
- password string
- )
-
- if auth.Username != "" && auth.Password != "" {
- username = auth.Username
- password = auth.Password
- }
-
- if auth.Auth != "" {
- data, err := base64.StdEncoding.DecodeString(auth.Auth)
- if err != nil {
- goto end
- }
-
- parts := strings.SplitN(string(data), ":", 2)
- if len(parts) != 2 {
- logger.V(2).Info("Skipping invalid parts", "length", len(parts), "parts", parts)
- goto end
- }
-
- if (username != "" && parts[0] != username) || (password != "" && parts[1] != password) {
- logger.V(2).Info("WARNING: Creds have more than two usernames or passwords")
- }
-
- username = parts[0]
- password = parts[1]
- }
-
-end:
- if username == "" && password == "" {
- return "", "", ""
- }
-
- basicAuth := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
- if auth.Auth != "" && basicAuth != auth.Auth {
- logger.Error(fmt.Errorf("base64-encoded auth does not match source"), "failed to parse auths JSON")
- }
- return username, password, basicAuth
-}
-
-// This is an ad-hoc implementation and not RFC compliant.
-// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate
-func parseAuthenticateHeader(headerValue string) (map[string]string, error) {
- authParams := make(map[string]string)
-
- parts := strings.Split(headerValue, " ")
- if len(parts) < 2 {
- return nil, fmt.Errorf("invalid WWW-Authenticate header format")
- }
- authParams["scheme"] = parts[0]
-
- parts = strings.Split(parts[1], ",")
- for _, part := range parts {
- keyVal := strings.SplitN(strings.TrimSpace(part), "=", 2)
- if len(keyVal) == 2 {
- key := strings.TrimSpace(keyVal[0])
- value := strings.Trim(strings.TrimSpace(keyVal[1]), `"`)
- authParams[key] = value
- }
- }
-
- return authParams, nil
-}
diff --git a/pkg/detectors/docker/docker_auth_config_integration_test.go b/pkg/detectors/docker/docker_auth_config_integration_test.go
deleted file mode 100644
index 998e7ea79d63..000000000000
--- a/pkg/detectors/docker/docker_auth_config_integration_test.go
+++ /dev/null
@@ -1,147 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package docker
-
-func TestDocker_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DOCKER")
- inactiveSecret := testSecrets.MustGetField("DOCKER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a docker secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Docker,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a docker secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Docker,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a docker secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Docker,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a docker secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Docker,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Docker.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Docker.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/docker/docker_auth_config_test.go b/pkg/detectors/docker/docker_auth_config_test.go
deleted file mode 100644
index 67ff7e7ff1b7..000000000000
--- a/pkg/detectors/docker/docker_auth_config_test.go
+++ /dev/null
@@ -1,293 +0,0 @@
-package docker
-
-import (
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
- "testing"
-)
-
-func TestDocker_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- // Kubernetes public test credentials
- // https://github.com/kubernetes/autoscaler/blob/f22b40eab867cbc52bdb15dc8768962e21d22837/vertical-pod-autoscaler/e2e/vendor/k8s.io/kubernetes/test/e2e/common/node/runtime.go#L283C1-L290C2
- {
- name: "GCP auth",
- input: `{
- "auths": {
- "https://gcr.io": {
- "auth": "X2pzb25fa2V5OnsKICAidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAogICJwcm9qZWN0X2lkIjogImF1dGhlbnRpY2F0ZWQtaW1hZ2UtcHVsbGluZyIsCiAgInByaXZhdGVfa2V5X2lkIjogImI5ZjJhNjY0YWE5YjIwNDg0Y2MxNTg2MDYzZmVmZGExOTIyNGFjM2IiLAogICJwcml2YXRlX2tleSI6ICItLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzdTSG5LVEVFaVlMamZcbkpmQVBHbUozd3JCY2VJNTBKS0xxS21GWE5RL3REWGJRK2g5YVl4aldJTDhEeDBKZTc0bVovS01uV2dYRjVLWlNcbm9BNktuSU85Yi9SY1NlV2VpSXRSekkzL1lYVitPNkNjcmpKSXl4anFWam5mVzJpM3NhMzd0OUE5VEZkbGZycm5cbjR6UkpiOWl4eU1YNGJMdHFGR3ZCMDNOSWl0QTNzVlo1ODhrb1FBZmgzSmhhQmVnTWorWjRSYko0aGVpQlFUMDNcbnZVbzViRWFQZVQ5RE16bHdzZWFQV2dydDZOME9VRGNBRTl4bGNJek11MjUzUG4vSzgySFpydEx4akd2UkhNVXhcbng0ZjhwSnhmQ3h4QlN3Z1NORit3OWpkbXR2b0wwRmE3ZGducFJlODZWRDY2ejNZenJqNHlLRXRqc2hLZHl5VWRcbkl5cVhoN1JSQWdNQkFBRUNnZ0VBT3pzZHdaeENVVlFUeEFka2wvSTVTRFVidi9NazRwaWZxYjJEa2FnbmhFcG9cbjFJajJsNGlWMTByOS9uenJnY2p5VlBBd3pZWk1JeDFBZVF0RDdoUzRHWmFweXZKWUc3NkZpWFpQUm9DVlB6b3VcbmZyOGRDaWFwbDV0enJDOWx2QXNHd29DTTdJWVRjZmNWdDdjRTEyRDNRS3NGNlo3QjJ6ZmdLS251WVBmK0NFNlRcbmNNMHkwaCtYRS9kMERvSERoVy96YU1yWEhqOFRvd2V1eXRrYmJzNGYvOUZqOVBuU2dET1lQd2xhbFZUcitGUWFcbkpSd1ZqVmxYcEZBUW14M0Jyd25rWnQzQ2lXV2lGM2QrSGk5RXRVYnRWclcxYjZnK1JRT0licWFtcis4YlJuZFhcbjZWZ3FCQWtKWjhSVnlkeFVQMGQxMUdqdU9QRHhCbkhCbmM0UW9rSXJFUUtCZ1FEMUNlaWN1ZGhXdGc0K2dTeGJcbnplanh0VjFONDFtZHVjQnpvMmp5b1dHbzNQVDh3ckJPL3lRRTM0cU9WSi9pZCs4SThoWjRvSWh1K0pBMDBzNmdcblRuSXErdi9kL1RFalk4MW5rWmlDa21SUFdiWHhhWXR4UjIxS1BYckxOTlFKS2ttOHRkeVh5UHFsOE1veUdmQ1dcbjJ2aVBKS05iNkhabnY5Q3lqZEo5ZzJMRG5RS0JnUUREcVN2eURtaGViOTIzSW96NGxlZ01SK205Z2xYVWdTS2dcbkVzZlllbVJmbU5XQitDN3ZhSXlVUm1ZNU55TXhmQlZXc3dXRldLYXhjK0krYnFzZmx6elZZdFpwMThNR2pzTURcbmZlZWZBWDZCWk1zVXQ3Qmw3WjlWSjg1bnRFZHFBQ0xwWitaLzN0SVJWdWdDV1pRMWhrbmxHa0dUMDI0SkVFKytcbk55SDFnM2QzUlFLQmdRQ1J2MXdKWkkwbVBsRklva0tGTkh1YTBUcDNLb1JTU1hzTURTVk9NK2xIckcxWHJtRjZcbkMwNGNTKzQ0N0dMUkxHOFVUaEpKbTRxckh0Ti9aK2dZOTYvMm1xYjRIakpORDM3TVhKQnZFYTN5ZUxTOHEvK1JcbjJGOU1LamRRaU5LWnhQcG84VzhOSlREWTVOa1BaZGh4a2pzSHdVNGRTNjZwMVRESUU0MGd0TFpaRFFLQmdGaldcbktyblFpTnEzOS9iNm5QOFJNVGJDUUFKbmR3anhTUU5kQTVmcW1rQTlhRk9HbCtqamsxQ1BWa0tNSWxLSmdEYkpcbk9heDl2OUc2Ui9NSTFIR1hmV3QxWU56VnRocjRIdHNyQTB0U3BsbWhwZ05XRTZWejZuQURqdGZQSnMyZUdqdlhcbmpQUnArdjhjY21MK3dTZzhQTGprM3ZsN2VlNXJsWWxNQndNdUdjUHhBb0dBZWRueGJXMVJMbVZubEFpSEx1L0xcbmxtZkF3RFdtRWlJMFVnK1BMbm9Pdk81dFE1ZDRXMS94RU44bFA0cWtzcGtmZk1Rbk5oNFNZR0VlQlQzMlpxQ1RcbkpSZ2YwWGpveXZ2dXA5eFhqTWtYcnBZL3ljMXpmcVRaQzBNTzkvMVVjMWJSR2RaMmR5M2xSNU5XYXA3T1h5Zk9cblBQcE5Gb1BUWGd2M3FDcW5sTEhyR3pNPVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwKICAiY2xpZW50X2VtYWlsIjogImltYWdlLXB1bGxpbmdAYXV0aGVudGljYXRlZC1pbWFnZS1wdWxsaW5nLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwKICAiY2xpZW50X2lkIjogIjExMzc5NzkxNDUzMDA3MzI3ODcxMiIsCiAgImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKICAidG9rZW5fdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsCiAgImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAogICJjbGllbnRfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9yb2JvdC92MS9tZXRhZGF0YS94NTA5L2ltYWdlLXB1bGxpbmclNDBhdXRoZW50aWNhdGVkLWltYWdlLXB1bGxpbmcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iCn0=",
- "email": "image-pulling@authenticated-image-pulling.iam.gserviceaccount.com"
- }
- }
-}`,
- want: []string{`{"registry":"https://gcr.io","auth":"X2pzb25fa2V5OnsKICAidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAogICJwcm9qZWN0X2lkIjogImF1dGhlbnRpY2F0ZWQtaW1hZ2UtcHVsbGluZyIsCiAgInByaXZhdGVfa2V5X2lkIjogImI5ZjJhNjY0YWE5YjIwNDg0Y2MxNTg2MDYzZmVmZGExOTIyNGFjM2IiLAogICJwcml2YXRlX2tleSI6ICItLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzdTSG5LVEVFaVlMamZcbkpmQVBHbUozd3JCY2VJNTBKS0xxS21GWE5RL3REWGJRK2g5YVl4aldJTDhEeDBKZTc0bVovS01uV2dYRjVLWlNcbm9BNktuSU85Yi9SY1NlV2VpSXRSekkzL1lYVitPNkNjcmpKSXl4anFWam5mVzJpM3NhMzd0OUE5VEZkbGZycm5cbjR6UkpiOWl4eU1YNGJMdHFGR3ZCMDNOSWl0QTNzVlo1ODhrb1FBZmgzSmhhQmVnTWorWjRSYko0aGVpQlFUMDNcbnZVbzViRWFQZVQ5RE16bHdzZWFQV2dydDZOME9VRGNBRTl4bGNJek11MjUzUG4vSzgySFpydEx4akd2UkhNVXhcbng0ZjhwSnhmQ3h4QlN3Z1NORit3OWpkbXR2b0wwRmE3ZGducFJlODZWRDY2ejNZenJqNHlLRXRqc2hLZHl5VWRcbkl5cVhoN1JSQWdNQkFBRUNnZ0VBT3pzZHdaeENVVlFUeEFka2wvSTVTRFVidi9NazRwaWZxYjJEa2FnbmhFcG9cbjFJajJsNGlWMTByOS9uenJnY2p5VlBBd3pZWk1JeDFBZVF0RDdoUzRHWmFweXZKWUc3NkZpWFpQUm9DVlB6b3VcbmZyOGRDaWFwbDV0enJDOWx2QXNHd29DTTdJWVRjZmNWdDdjRTEyRDNRS3NGNlo3QjJ6ZmdLS251WVBmK0NFNlRcbmNNMHkwaCtYRS9kMERvSERoVy96YU1yWEhqOFRvd2V1eXRrYmJzNGYvOUZqOVBuU2dET1lQd2xhbFZUcitGUWFcbkpSd1ZqVmxYcEZBUW14M0Jyd25rWnQzQ2lXV2lGM2QrSGk5RXRVYnRWclcxYjZnK1JRT0licWFtcis4YlJuZFhcbjZWZ3FCQWtKWjhSVnlkeFVQMGQxMUdqdU9QRHhCbkhCbmM0UW9rSXJFUUtCZ1FEMUNlaWN1ZGhXdGc0K2dTeGJcbnplanh0VjFONDFtZHVjQnpvMmp5b1dHbzNQVDh3ckJPL3lRRTM0cU9WSi9pZCs4SThoWjRvSWh1K0pBMDBzNmdcblRuSXErdi9kL1RFalk4MW5rWmlDa21SUFdiWHhhWXR4UjIxS1BYckxOTlFKS2ttOHRkeVh5UHFsOE1veUdmQ1dcbjJ2aVBKS05iNkhabnY5Q3lqZEo5ZzJMRG5RS0JnUUREcVN2eURtaGViOTIzSW96NGxlZ01SK205Z2xYVWdTS2dcbkVzZlllbVJmbU5XQitDN3ZhSXlVUm1ZNU55TXhmQlZXc3dXRldLYXhjK0krYnFzZmx6elZZdFpwMThNR2pzTURcbmZlZWZBWDZCWk1zVXQ3Qmw3WjlWSjg1bnRFZHFBQ0xwWitaLzN0SVJWdWdDV1pRMWhrbmxHa0dUMDI0SkVFKytcbk55SDFnM2QzUlFLQmdRQ1J2MXdKWkkwbVBsRklva0tGTkh1YTBUcDNLb1JTU1hzTURTVk9NK2xIckcxWHJtRjZcbkMwNGNTKzQ0N0dMUkxHOFVUaEpKbTRxckh0Ti9aK2dZOTYvMm1xYjRIakpORDM3TVhKQnZFYTN5ZUxTOHEvK1JcbjJGOU1LamRRaU5LWnhQcG84VzhOSlREWTVOa1BaZGh4a2pzSHdVNGRTNjZwMVRESUU0MGd0TFpaRFFLQmdGaldcbktyblFpTnEzOS9iNm5QOFJNVGJDUUFKbmR3anhTUU5kQTVmcW1rQTlhRk9HbCtqamsxQ1BWa0tNSWxLSmdEYkpcbk9heDl2OUc2Ui9NSTFIR1hmV3QxWU56VnRocjRIdHNyQTB0U3BsbWhwZ05XRTZWejZuQURqdGZQSnMyZUdqdlhcbmpQUnArdjhjY21MK3dTZzhQTGprM3ZsN2VlNXJsWWxNQndNdUdjUHhBb0dBZWRueGJXMVJMbVZubEFpSEx1L0xcbmxtZkF3RFdtRWlJMFVnK1BMbm9Pdk81dFE1ZDRXMS94RU44bFA0cWtzcGtmZk1Rbk5oNFNZR0VlQlQzMlpxQ1RcbkpSZ2YwWGpveXZ2dXA5eFhqTWtYcnBZL3ljMXpmcVRaQzBNTzkvMVVjMWJSR2RaMmR5M2xSNU5XYXA3T1h5Zk9cblBQcE5Gb1BUWGd2M3FDcW5sTEhyR3pNPVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwKICAiY2xpZW50X2VtYWlsIjogImltYWdlLXB1bGxpbmdAYXV0aGVudGljYXRlZC1pbWFnZS1wdWxsaW5nLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwKICAiY2xpZW50X2lkIjogIjExMzc5NzkxNDUzMDA3MzI3ODcxMiIsCiAgImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKICAidG9rZW5fdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsCiAgImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAogICJjbGllbnRfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9yb2JvdC92MS9tZXRhZGF0YS94NTA5L2ltYWdlLXB1bGxpbmclNDBhdXRoZW50aWNhdGVkLWltYWdlLXB1bGxpbmcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iCn0="}`},
- },
- // Relies on the base64 decoder, which isn't present in this test (yet?)
- // {
- // name: "kubernetes .dockerconfigjson",
- // input: `apiVersion: v1
- //data:
- // .dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL2djci5pbyI6eyJ1c2VybmFtZSI6Il9qc29uX2tleSIsInBhc3N3b3JkIjoie1xuICBcInR5cGVcIjogXCJzZXJ2aWNlX2FjY291bnRcIixcbiAgXCJwcm9qZWN0X2lkXCI6IFwiY29uc3RhbnQtY3ViaXN0LTE3MzEyM1wiLFxuICBcInByaXZhdGVfa2V5X2lkXCI6IFwiYWRiMzY3M2NiOTkzNzkyNjZiY2MxZDU1YmIxZTdiZDFlYzM5NGI1Y1wiLFxuICBcInByaXZhdGVfa2V5XCI6IFwiLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tXFxuTUlJRXZRSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2N3Z2dTakFnRUFBb0lCQVFDNm8zN0o4S2kxUWp3RVxcbnhNT3ROUVZaK2xsWUxIdlNXV2tDeXp1a3JwbHdZRU9KRk5VR00yQ3NySHpjM0pDUDhGYWo1RVRHMjlvT1pLVkJcXG5MSjU3eVdKSEpyekhIb2JyOHNsNytpcjRjYUovSzNiS2lybmZWYTZFeXk5azFIa0RMSlZ4T1lsaXFTbkdtRlZ5XFxuQ3lpYXltNTI1V3VqanZIQkRaZUdsYzlqb1RLMG9yQXYvUCthZzhleUUvY05DS0FwTkk4ZTFXYmlhMFNCdWEwblxcblVZbFB1RXRxdzJ3NDhJbkh6akVQY0VmdENzWjBOZGhkY3hTdVNuSVB5NW9ua2JuVXhZWnAzUjF3TmQ3eDdaQk5cXG5ESmFCWEJTMlVkR1M0ditzeWJVQlU1aXFBckRNbVNmWUwxN09TU3ZzdEZSdVEydkJaa0M3TU96RWd2MUlIZjBXXFxubzlOSzBFaHZBZ01CQUFFQ2dnRUFDUm1MbkZaSzJORHFrdU9kRkJ3dnIwYTdoY2NLeW5pOWhxWURaSERNTTduSVxcbmU5aUkxN2ZpNWgyMWdNeVM1OUcwc21KTGV0UDJwUmtCemFtdjdjMGwwNGp2VDFpM3IxZ0pFWU1Oc1V0VHZFRG1cXG42OUorWkRDTjc3K1FYS21DQ2tZKzRHUmVieHhjV0doNC9MUjZrd0Y5Qi9oV1JTL2xBdlZNc1ZmVjRyK3JTZVNjXFxubU1KOTRBUTROM3hyV0VRc3Vpd1ZIZldMdElMTWZGN1JoV3VzdjJiZ1gvRCs0ajdISHRoODVrYlcxSzR0MnFkN1xcbkIwaEJEcVlQTEtjYzJVNkNJR0NRZ1h3THNlYUUxRkptYWpsdnNVK0pXdmY2MmZTNk8wSlVMeVFLMzZkczZkRlJcXG5qaDg1TWJsZVlHMWdpaFpPTXJtcENvWklUazdFT01lU2pQZ0VaWG5pVVFLQmdRRDNtUHJrZmVKVWNXWmpNZndCXFxuYnJJNE1NRWl2R1JJVDR5RzZxZHZpZFIrd1U3djMxbG1Oa1A3S2s4K1hoQWtGMk1pQkRSakFGVm8rNHQ3a3paRFxcbk45Zk9NSlgwakZnUVpLajFuR1gxekZ2ZERqRTh3ZWRTN2ZMV3BOczJlZm5GWUpQRm5SRU16eG81VWpGNTZ4V1pcXG5ZQmI2VHlNaTNRa1lEblA0WTVjTUZCVUpiUUtCZ1FEQStPNDlUc2EyYWdWUnJYbC8xRDNzWHd0UjdKSGhuRURkXFxuNWlZM0FtOVQxV2pVVTE1T2lwTUxOaXpBb3lRWGlKRVlaMmNuRHZnbHdkNEsvMWFLbityc3hzWUdFZjhoWVBIclxcbkJoN3FueW44SzJseTJoakUxY0xpVFg4NEVnd1VMcFJjeGo3bkM0ZWFLOEdJeUdLNnZrR3NoNCs1bnJLVFlkaUtcXG5MeUhSMUc2cnl3S0JnUURnLzJqSGFNbmEySzRsYUUvTWNXNk05MmtiQ3IzS3BGZGNaeksrZmk3Vy9RMmhsNEtqXFxuQ3A4ZVNDVjQxSHV3Z0h3NmRqMncxYVhINEFheHhtWWlFVVlQL2tEVzJRNVIzMWRXMHNnbzVJdDZSeUpoUndmU1xcbmFaOHFoT2NjQ3gzNXlqaWU5SXVBNjFhMlRrWGR0ODZKOFRNUVJnZjA3NDRMQ1Y5RGtpUzUraW5meFFLQmdFMVdcXG5ObHlacXFmR203VWRPZmxSL1RNeThCMTRHd3I1RFVJaEQ2V3lNeDI5QkpNN2lpc2QvRXBjL3RpQlNXQ3BHY1ZYXFxuQTQ4eXY1NmFNTHZsa3pCaFlNeGQ2VlRiZDQxUUJnUXo0c1lTM2Nlek9rS09SNmp6Sm5SOXJJT3pMK1lTdU9EcFxcbmpxSVlDOU5zdjlacXdLNm91emRDNlFYeUpRMU9CSE4wNmkvbTNDZTdBb0dBU01wRStscDlxV2ZWYXlGV2tlWVBcXG5OOFhId2FNUWNkT0ZkbDZFdlF0ZWtQY0xiQ1F6UzRSdEhBT01NTDN5ci9DQUk5SmZkanhWMHdicW1oNlJ3WFAzXFxuKzhkOVJpNjhsMGV3NUhLMDJWRHFhZE8vOTJhaHNrNmYxV1ZOL0dMcFg4Yk9NZEZFdnJOS09zUVk0RW9DV0JTa1xcblF1ZmRBdFZueE1UZG9ydTNxY0N4RG1vPVxcbi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS1cXG5cIixcbiAgXCJjbGllbnRfZW1haWxcIjogXCJrZi1hY2NvdW50QGNvbnN0YW50LWN1YmlzdC0xNzMxMjMuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb21cIixcbiAgXCJjbGllbnRfaWRcIjogXCIxMDkyODcyODAxMzE5ODQ2MTA2MTZcIixcbiAgXCJhdXRoX3VyaVwiOiBcImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoXCIsXG4gIFwidG9rZW5fdXJpXCI6IFwiaHR0cHM6Ly9vYXV0aDIuZ29vZ2xlYXBpcy5jb20vdG9rZW5cIixcbiAgXCJhdXRoX3Byb3ZpZGVyX3g1MDlfY2VydF91cmxcIjogXCJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHNcIixcbiAgXCJjbGllbnRfeDUwOV9jZXJ0X3VybFwiOiBcImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL3JvYm90L3YxL21ldGFkYXRhL3g1MDkva2YtYWNjb3VudCU0MGNvbnN0YW50LWN1YmlzdC0xNzMxMjMuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb21cIlxufSIsImVtYWlsIjoia2YtYWNjb3VudEBjb25zdGFudC1jdWJpc3QtMTczMTIzLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwiYXV0aCI6IlgycHpiMjVmYTJWNU9uc0tJQ0FpZEhsd1pTSTZJQ0p6WlhKMmFXTmxYMkZqWTI5MWJuUWlMQW9nSUNKd2NtOXFaV04wWDJsa0lqb2dJbU52Ym5OMFlXNTBMV04xWW1semRDMHhOek14TWpNaUxBb2dJQ0p3Y21sMllYUmxYMnRsZVY5cFpDSTZJQ0poWkdJek5qY3pZMkk1T1RNM09USTJObUpqWXpGa05UVmlZakZsTjJKa01XVmpNemswWWpWaklpd0tJQ0FpY0hKcGRtRjBaVjlyWlhraU9pQWlMUzB0TFMxQ1JVZEpUaUJRVWtsV1FWUkZJRXRGV1MwdExTMHRYRzVOU1VsRmRsRkpRa0ZFUVU1Q1oydHhhR3RwUnpsM01FSkJVVVZHUVVGVFEwSkxZM2RuWjFOcVFXZEZRVUZ2U1VKQlVVTTJiek0zU2poTGFURlJhbmRGWEc1NFRVOTBUbEZXV2l0c2JGbE1TSFpUVjFkclEzbDZkV3R5Y0d4M1dVVlBTa1pPVlVkTk1rTnpja2g2WXpOS1ExQTRSbUZxTlVWVVJ6STViMDlhUzFaQ1hHNU1TalUzZVZkS1NFcHlla2hJYjJKeU9ITnNOeXRwY2pSallVb3ZTek5pUzJseWJtWldZVFpGZVhrNWF6RklhMFJNU2xaNFQxbHNhWEZUYmtkdFJsWjVYRzVEZVdsaGVXMDFNalZYZFdwcWRraENSRnBsUjJ4ak9XcHZWRXN3YjNKQmRpOVFLMkZuT0dWNVJTOWpUa05MUVhCT1NUaGxNVmRpYVdFd1UwSjFZVEJ1WEc1VldXeFFkVVYwY1hjeWR6UTRTVzVJZW1wRlVHTkZablJEYzFvd1RtUm9aR040VTNWVGJrbFFlVFZ2Ym10aWJsVjRXVnB3TTFJeGQwNWtOM2czV2tKT1hHNUVTbUZDV0VKVE1sVmtSMU0wZGl0emVXSlZRbFUxYVhGQmNrUk5iVk5tV1V3eE4wOVRVM1p6ZEVaU2RWRXlka0phYTBNM1RVOTZSV2QyTVVsSVpqQlhYRzV2T1U1TE1FVm9ka0ZuVFVKQlFVVkRaMmRGUVVOU2JVeHVSbHBMTWs1RWNXdDFUMlJHUW5kMmNqQmhOMmhqWTB0NWJtazVhSEZaUkZwSVJFMU5OMjVKWEc1bE9XbEpNVGRtYVRWb01qRm5UWGxUTlRsSE1ITnRTa3hsZEZBeWNGSnJRbnBoYlhZM1l6QnNNRFJxZGxReGFUTnlNV2RLUlZsTlRuTlZkRlIyUlVSdFhHNDJPVW9yV2tSRFRqYzNLMUZZUzIxRFEydFpLelJIVW1WaWVIaGpWMGRvTkM5TVVqWnJkMFk1UWk5b1YxSlRMMnhCZGxaTmMxWm1WalJ5SzNKVFpWTmpYRzV0VFVvNU5FRlJORTR6ZUhKWFJWRnpkV2wzVmtobVYweDBTVXhOWmtZM1VtaFhkWE4yTW1KbldDOUVLelJxTjBoSWRHZzROV3RpVnpGTE5IUXljV1EzWEc1Q01HaENSSEZaVUV4TFkyTXlWVFpEU1VkRFVXZFlkMHh6WldGRk1VWktiV0ZxYkhaelZTdEtWM1ptTmpKbVV6WlBNRXBWVEhsUlN6TTJaSE0yWkVaU1hHNXFhRGcxVFdKc1pWbEhNV2RwYUZwUFRYSnRjRU52V2tsVWF6ZEZUMDFsVTJwUVowVmFXRzVwVlZGTFFtZFJSRE50VUhKclptVktWV05YV21wTlpuZENYRzVpY2trMFRVMUZhWFpIVWtsVU5IbEhObkZrZG1sa1VpdDNWVGQyTXpGc2JVNXJVRGRMYXpncldHaEJhMFl5VFdsQ1JGSnFRVVpXYnlzMGREZHJlbHBFWEc1T09XWlBUVXBZTUdwR1oxRmFTMm94YmtkWU1YcEdkbVJFYWtVNGQyVmtVemRtVEZkd1RuTXlaV1p1UmxsS1VFWnVVa1ZOZW5odk5WVnFSalUyZUZkYVhHNVpRbUkyVkhsTmFUTlJhMWxFYmxBMFdUVmpUVVpDVlVwaVVVdENaMUZFUVN0UE5EbFVjMkV5WVdkV1VuSlliQzh4UkROeldIZDBVamRLU0dodVJVUmtYRzQxYVZrelFXMDVWREZYYWxWVk1UVlBhWEJOVEU1cGVrRnZlVkZZYVVwRldWb3lZMjVFZG1kc2QyUTBTeTh4WVV0dUszSnplSE5aUjBWbU9HaFpVRWh5WEc1Q2FEZHhibmx1T0VzeWJIa3lhR3BGTVdOTWFWUllPRFJGWjNkVlRIQlNZM2hxTjI1RE5HVmhTemhIU1hsSFN6WjJhMGR6YURRck5XNXlTMVJaWkdsTFhHNU1lVWhTTVVjMmNubDNTMEpuVVVSbkx6SnFTR0ZOYm1FeVN6UnNZVVV2VFdOWE5rMDVNbXRpUTNJelMzQkdaR05hZWtzclptazNWeTlSTW1oc05FdHFYRzVEY0RobFUwTldOREZJZFhkblNIYzJaR295ZHpGaFdFZzBRV0Y0ZUcxWmFVVlZXVkF2YTBSWE1sRTFVak14WkZjd2MyZHZOVWwwTmxKNVNtaFNkMlpUWEc1aFdqaHhhRTlqWTBONE16VjVhbWxsT1VsMVFUWXhZVEpVYTFoa2REZzJTamhVVFZGU1oyWXdOelEwVEVOV09VUnJhVk0xSzJsdVpuaFJTMEpuUlRGWFhHNU9iSGxhY1hGbVIyMDNWV1JQWm14U0wxUk5lVGhDTVRSSGQzSTFSRlZKYUVRMlYzbE5lREk1UWtwTk4ybHBjMlF2UlhCakwzUnBRbE5YUTNCSFkxWllYRzVCTkRoNWRqVTJZVTFNZG14cmVrSm9XVTE0WkRaV1ZHSmtOREZSUW1kUmVqUnpXVk16WTJWNlQydExUMUkyYW5wS2JsSTVja2xQZWt3cldWTjFUMFJ3WEc1cWNVbFpRemxPYzNZNVduRjNTelp2ZFhwa1F6WlJXSGxLVVRGUFFraE9NRFpwTDIwelEyVTNRVzlIUVZOTmNFVXJiSEE1Y1ZkbVZtRjVSbGRyWlZsUVhHNU9PRmhJZDJGTlVXTmtUMFprYkRaRmRsRjBaV3RRWTB4aVExRjZVelJTZEVoQlQwMU5URE41Y2k5RFFVazVTbVprYW5oV01IZGljVzFvTmxKM1dGQXpYRzRyT0dRNVVtazJPR3d3WlhjMVNFc3dNbFpFY1dGa1R5ODVNbUZvYzJzMlpqRlhWazR2UjB4d1dEaGlUMDFrUmtWMmNrNUxUM05SV1RSRmIwTlhRbE5yWEc1UmRXWmtRWFJXYm5oTlZHUnZjblV6Y1dORGVFUnRiejFjYmkwdExTMHRSVTVFSUZCU1NWWkJWRVVnUzBWWkxTMHRMUzFjYmlJc0NpQWdJbU5zYVdWdWRGOWxiV0ZwYkNJNklDSnJaaTFoWTJOdmRXNTBRR052Ym5OMFlXNTBMV04xWW1semRDMHhOek14TWpNdWFXRnRMbWR6WlhKMmFXTmxZV05qYjNWdWRDNWpiMjBpTEFvZ0lDSmpiR2xsYm5SZmFXUWlPaUFpTVRBNU1qZzNNamd3TVRNeE9UZzBOakV3TmpFMklpd0tJQ0FpWVhWMGFGOTFjbWtpT2lBaWFIUjBjSE02THk5aFkyTnZkVzUwY3k1bmIyOW5iR1V1WTI5dEwyOHZiMkYxZEdneUwyRjFkR2dpTEFvZ0lDSjBiMnRsYmw5MWNta2lPaUFpYUhSMGNITTZMeTl2WVhWMGFESXVaMjl2WjJ4bFlYQnBjeTVqYjIwdmRHOXJaVzRpTEFvZ0lDSmhkWFJvWDNCeWIzWnBaR1Z5WDNnMU1EbGZZMlZ5ZEY5MWNtd2lPaUFpYUhSMGNITTZMeTkzZDNjdVoyOXZaMnhsWVhCcGN5NWpiMjB2YjJGMWRHZ3lMM1l4TDJObGNuUnpJaXdLSUNBaVkyeHBaVzUwWDNnMU1EbGZZMlZ5ZEY5MWNtd2lPaUFpYUhSMGNITTZMeTkzZDNjdVoyOXZaMnhsWVhCcGN5NWpiMjB2Y205aWIzUXZkakV2YldWMFlXUmhkR0V2ZURVd09TOXJaaTFoWTJOdmRXNTBKVFF3WTI5dWMzUmhiblF0WTNWaWFYTjBMVEUzTXpFeU15NXBZVzB1WjNObGNuWnBZMlZoWTJOdmRXNTBMbU52YlNJS2ZRPT0ifX19
- //kind: Secret
- //metadata:
- // name: docker-secret
- //type: kubernetes.io/dockerconfigjson`,
- // want: []string{"3aBcDFE5678901234567890_1a2b3c4d"},
- // },
- {
- name: "DOCKER_AUTH_CONFIG escaped",
- input: `[[runners]]
- name = "docker-test@236"
- url = "http://10.88.26.237:80"
- executor = "docker"
- environment = ["DOCKER_AUTH_CONFIG={\"auths\":{\"docker.contoso.com.tw:8083\":{\"auth\":\"c2Zjcy50ZXN0ZXI6c2Zjcw==\"}}}"]
- [runners.custom_build_dir]
- [runners.cache]
- Insecure = false`,
- want: []string{`{"registry":"docker.contoso.com.tw:8083","auth":"c2Zjcy50ZXN0ZXI6c2Zjcw=="}`},
- },
- {
- name: "multiple escapes",
- input: `[[runners]]
- environment = ["DOCKER_AUTH_CONFIG={\\\"auths\\\":{\\\"docker.contoso.com.tw:8081\\\":{\\\"auth\\\":\\\"c2Zjcy50ZXN0ZXI6c2Zjcw==\\\"}}}"]`,
- want: []string{`{"registry":"docker.contoso.com.tw:8081","auth":"c2Zjcy50ZXN0ZXI6c2Zjcw=="}`},
- },
- {
- name: "DOCKER_AUTH_CONFIG",
- input: `variables:
- DOCKER_DRIVER: overlay2
- DOCKER_AUTH_CONFIG: '{"auths": {"local-docker.artifactory.university.edu.au": {"auth": "YmFtYm9vOmpoMkh6UnNRU3pad3liaDc="}}}'
-`,
- want: []string{`{"registry":"local-docker.artifactory.university.edu.au","auth":"YmFtYm9vOmpoMkh6UnNRU3pad3liaDc="}`},
- },
- {
- name: "empty email string",
- input: `{
- "auths": {
- "quay.io": {
- "auth": "dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA=",
- "email": ""
- }
- }
-}`,
- want: []string{`{"registry":"quay.io","auth":"dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA="}`},
- },
- {
- name: "docker.io registry",
- input: `{"auths":{"docker.io":{"auth": "dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA="}}}`,
- want: []string{`{"registry":"index.docker.io","auth":"dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA="}`},
- },
- {
- name: "registry with slashes",
- input: `{"auths":{"https://index.docker.io/v2/":{"auth": "dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA="}}}`,
- want: []string{`{"registry":"https://index.docker.io/v2/","auth":"dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA="}`},
- },
- {
- name: "literal newlines",
- input: `{\n\"auths\": {\n\"registry.company.com\": {\n\"username\": \"conexp\",\n\"password\": \"FTA@CNCF0n@zure3\",\n\"email\": \"user@mycompany.com\",\n\"auth\": \"Y29uZXhwOkZUQUBDTkNGMG5AenVyZTM=\"\n}\n}\n}\n`,
- want: []string{`{"registry":"registry.company.com","auth":"Y29uZXhwOkZUQUBDTkNGMG5AenVyZTM="}`},
- },
- {
- name: "literal newlines and tabs",
- input: ` config.json: "{\n\t\"auths\": {\n\t\t\"https://index.docker.io/v2/\": {\n\t\t\t\"auth\":\"Y29uZXhwOkZUQUBDTkNGMG5AenVyZTM=\"\n\t\t}\n\t}\n}"`,
- want: []string{`{"registry":"https://index.docker.io/v2/","auth":"Y29uZXhwOkZUQUBDTkNGMG5AenVyZTM="}`},
- },
- {
- name: "content after last }",
- // This is base64-encoded, however, that doesn't get detected in these tests.
- //input: `{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","request":{"uid":"b9d17c49-1b2c-421a-8ae8-3b3d252d2f61","kind":{"group":"","version":"v1","kind":"Secret"},"resource":{"group":"","version":"v1","resource":"secrets"},"requestKind":{"group":"","version":"v1","kind":"Secret"},"requestResource":{"group":"","version":"v1","resource":"secrets"},"name":"regcred","namespace":"test-webhooks","operation":"CREATE","userInfo":{"username":"kube:admin","groups":["system:cluster-admins","system:authenticated"],"extra":{"scopes.authorization.openshift.io":["user:full"]}},"object":{"kind":"Secret","apiVersion":"v1","metadata":{"name":"regcred","namespace":"test-webhooks","uid":"544674ac-f0fb-4a30-994b-eab579e1f418","creationTimestamp":"2022-05-03T15:16:55Z","managedFields":[{"manager":"kubectl-create","operation":"Update","apiVersion":"v1","time":"2022-05-03T15:16:55Z","fieldsType":"FieldsV1","fieldsV1":{"f:data":{".":{},"f:.dockerconfigjson":{}},"f:type":{}}}]},"data":{".dockerconfigjson":"eyJhdXRocyI6eyJxdWF5LmlvIjp7InVzZXJuYW1lIjoiMTIzIiwicGFzc3dvcmQiOiIxMjMiLCJhdXRoIjoiTVRJek9qRXlNdz09In19fQ=="},"type":"kubernetes.io/dockerconfigjson"},"oldObject":null,"dryRun":false,"options":{"kind":"CreateOptions","apiVersion":"meta.k8s.io/v1","fieldManager":"kubectl-create"}}}`,
- input: `{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","request":{"uid":"b9d17c49-1b2c-421a-8ae8-3b3d252d2f61","kind":{"group":"","version":"v1","kind":"Secret"},"resource":{"group":"","version":"v1","resource":"secrets"},"requestKind":{"group":"","version":"v1","kind":"Secret"},"requestResource":{"group":"","version":"v1","resource":"secrets"},"name":"regcred","namespace":"test-webhooks","operation":"CREATE","userInfo":{"username":"kube:admin","groups":["system:cluster-admins","system:authenticated"],"extra":{"scopes.authorization.openshift.io":["user:full"]}},"object":{"kind":"Secret","apiVersion":"v1","metadata":{"name":"regcred","namespace":"test-webhooks","uid":"544674ac-f0fb-4a30-994b-eab579e1f418","creationTimestamp":"2022-05-03T15:16:55Z","managedFields":[{"manager":"kubectl-create","operation":"Update","apiVersion":"v1","time":"2022-05-03T15:16:55Z","fieldsType":"FieldsV1","fieldsV1":{"f:data":{".":{},"f:.dockerconfigjson":{}},"f:type":{}}}]},"data":{".dockerconfigjson":"{"auths":{"quay.io":{"username":"123","password":"123","auth":"MTIzOjEyMw=="}}}"},"type":"kubernetes.io/dockerconfigjson"},"oldObject":null,"dryRun":false,"options":{"kind":"CreateOptions","apiVersion":"meta.k8s.io/v1","fieldManager":"kubectl-create"}}}`,
- want: []string{`{"registry":"quay.io","auth":"MTIzOjEyMw=="}`},
- },
-
- // False-positives
- {
- name: "registry.example.com",
- input: `1. Modify the runner's config.toml file as follows:
-
- [[runners]]
- environment = ["DOCKER_AUTH_CONFIG={\"auths\":{\"registry.example.com:5000\":{\"auth\":\"bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=\"}}}"]
- `,
- },
- {
- name: "",
- input: `sudo gitlab-runner register -n \
- --url https://gitlab.contoso.cn:8443/ \
- --registration-token ****** \
- --docker-extra-hosts "gitlab.contoso.cn:10.202.101.22" \
- --tag-list "golang-test" \
- --executor docker \
- --description "229 contoso golang test" \
- --docker-image "docker:19.03.1" \
- --docker-privileged \
- --env "DOCKER_AUTH_CONFIG={\"auths\": {\"registry.contoso123.cn:5000\": {\"auth\": \"******\"},\"registry.contoso.com.cn\": {\"auth\": \"******\"}}}" \
- --custom_build_dir-enabled=true `,
- },
- // TODO: There's currently no solution to detect/ignore environment variables or placeholders.
- // {
- // name: "variables",
- // input: `analyze_reports:
- //stage: post
- //image: registry.gitlab.com/detecttechnologies/software/webapps/t-pulse/web/tpulse-msa/tpulse-msa-cicd:production
- //variables:
- // DOCKER_AUTH_CONFIG: '{"auths":{"registry.gitlab.com":{"username":"${CI_CD_API_USER}","password":"${CI_CD_API_TOKEN}"}}}'`,
- // },
-
- {
- name: "empty registry",
- input: `The command outputs the following:
-* A non-bootable configuration ISO ( agentconfig.noarch.iso)
-* 'auth' directory: contains kubeconfig and kubeadmin-password
-
-Note: for disconnected environments, specify a dummy pull-secret in install-config.yaml (e.g. '{"auths":{"":{"auth":"dXNlcjpwYXNz"}}}').`,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
-
-func Test_ParseAuth(t *testing.T) {
- tests := map[dockerAuth]string{
- // Only auth
- dockerAuth{
- Auth: "Ym9iOnMzY3IzdHBAc3N3MHJkIQ==",
- }: "bob:s3cr3tp@ssw0rd!",
- // Auth with colon
- dockerAuth{
- Auth: "OTM5MDQ5YjQtNTllMS00YzlhLWJlYzgtMjAyZTAxZjc2MWFlOjZCLkpFOmZPT2hvLTI3P244TlYybDZqQS9UdjBMd1hm",
- }: "939049b4-59e1-4c9a-bec8-202e01f761ae:6B.JE:fOOho-27?n8NV2l6jA/Tv0LwXf",
- // Only username + password
- dockerAuth{
- Username: "my_username",
- Password: "my_password",
- }: "my_username:my_password",
- // Auth and username+password
- dockerAuth{
- Auth: "bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ==",
- Username: "my_username",
- Password: "my_password",
- }: "my_username:my_password",
- // Kubernetes public test credentials
- // https://github.com/kubernetes/autoscaler/blob/f22b40eab867cbc52bdb15dc8768962e21d22837/vertical-pod-autoscaler/e2e/vendor/k8s.io/kubernetes/test/e2e/common/node/runtime.go#L283C1-L290C2
- dockerAuth{
- Auth: `X2pzb25fa2V5OnsKICAidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAogICJwcm9qZWN0X2lkIjogImF1dGhlbnRpY2F0ZWQtaW1hZ2UtcHVsbGluZyIsCiAgInByaXZhdGVfa2V5X2lkIjogImI5ZjJhNjY0YWE5YjIwNDg0Y2MxNTg2MDYzZmVmZGExOTIyNGFjM2IiLAogICJwcml2YXR
-lX2tleSI6ICItLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzdTSG5LVEVFaVlMamZcbkpmQVBHbUozd3JCY2VJNTBKS0xxS21GWE5RL3REWGJRK2g5YVl4aldJTDhEeDBKZTc0bVovS01uV2dYRjVLWlNcbm9BNktuSU85Yi9SY1NlV2V
-pSXRSekkzL1lYVitPNkNjcmpKSXl4anFWam5mVzJpM3NhMzd0OUE5VEZkbGZycm5cbjR6UkpiOWl4eU1YNGJMdHFGR3ZCMDNOSWl0QTNzVlo1ODhrb1FBZmgzSmhhQmVnTWorWjRSYko0aGVpQlFUMDNcbnZVbzViRWFQZVQ5RE16bHdzZWFQV2dydDZOME9VRGNBRTl4bGNJek11MjUzUG4vSzgySFpydEx4akd2UkhNVXhcbng0Zjh
-wSnhmQ3h4QlN3Z1NORit3OWpkbXR2b0wwRmE3ZGducFJlODZWRDY2ejNZenJqNHlLRXRqc2hLZHl5VWRcbkl5cVhoN1JSQWdNQkFBRUNnZ0VBT3pzZHdaeENVVlFUeEFka2wvSTVTRFVidi9NazRwaWZxYjJEa2FnbmhFcG9cbjFJajJsNGlWMTByOS9uenJnY2p5VlBBd3pZWk1JeDFBZVF0RDdoUzRHWmFweXZKWUc3NkZpWFpQUm9
-DVlB6b3VcbmZyOGRDaWFwbDV0enJDOWx2QXNHd29DTTdJWVRjZmNWdDdjRTEyRDNRS3NGNlo3QjJ6ZmdLS251WVBmK0NFNlRcbmNNMHkwaCtYRS9kMERvSERoVy96YU1yWEhqOFRvd2V1eXRrYmJzNGYvOUZqOVBuU2dET1lQd2xhbFZUcitGUWFcbkpSd1ZqVmxYcEZBUW14M0Jyd25rWnQzQ2lXV2lGM2QrSGk5RXRVYnRWclcxYjZ
-nK1JRT0licWFtcis4YlJuZFhcbjZWZ3FCQWtKWjhSVnlkeFVQMGQxMUdqdU9QRHhCbkhCbmM0UW9rSXJFUUtCZ1FEMUNlaWN1ZGhXdGc0K2dTeGJcbnplanh0VjFONDFtZHVjQnpvMmp5b1dHbzNQVDh3ckJPL3lRRTM0cU9WSi9pZCs4SThoWjRvSWh1K0pBMDBzNmdcblRuSXErdi9kL1RFalk4MW5rWmlDa21SUFdiWHhhWXR4UjI
-xS1BYckxOTlFKS2ttOHRkeVh5UHFsOE1veUdmQ1dcbjJ2aVBKS05iNkhabnY5Q3lqZEo5ZzJMRG5RS0JnUUREcVN2eURtaGViOTIzSW96NGxlZ01SK205Z2xYVWdTS2dcbkVzZlllbVJmbU5XQitDN3ZhSXlVUm1ZNU55TXhmQlZXc3dXRldLYXhjK0krYnFzZmx6elZZdFpwMThNR2pzTURcbmZlZWZBWDZCWk1zVXQ3Qmw3WjlWSjg
-1bnRFZHFBQ0xwWitaLzN0SVJWdWdDV1pRMWhrbmxHa0dUMDI0SkVFKytcbk55SDFnM2QzUlFLQmdRQ1J2MXdKWkkwbVBsRklva0tGTkh1YTBUcDNLb1JTU1hzTURTVk9NK2xIckcxWHJtRjZcbkMwNGNTKzQ0N0dMUkxHOFVUaEpKbTRxckh0Ti9aK2dZOTYvMm1xYjRIakpORDM3TVhKQnZFYTN5ZUxTOHEvK1JcbjJGOU1LamRRaU5
-LWnhQcG84VzhOSlREWTVOa1BaZGh4a2pzSHdVNGRTNjZwMVRESUU0MGd0TFpaRFFLQmdGaldcbktyblFpTnEzOS9iNm5QOFJNVGJDUUFKbmR3anhTUU5kQTVmcW1rQTlhRk9HbCtqamsxQ1BWa0tNSWxLSmdEYkpcbk9heDl2OUc2Ui9NSTFIR1hmV3QxWU56VnRocjRIdHNyQTB0U3BsbWhwZ05XRTZWejZuQURqdGZQSnMyZUdqdlh
-cbmpQUnArdjhjY21MK3dTZzhQTGprM3ZsN2VlNXJsWWxNQndNdUdjUHhBb0dBZWRueGJXMVJMbVZubEFpSEx1L0xcbmxtZkF3RFdtRWlJMFVnK1BMbm9Pdk81dFE1ZDRXMS94RU44bFA0cWtzcGtmZk1Rbk5oNFNZR0VlQlQzMlpxQ1RcbkpSZ2YwWGpveXZ2dXA5eFhqTWtYcnBZL3ljMXpmcVRaQzBNTzkvMVVjMWJSR2RaMmR5M2x
-SNU5XYXA3T1h5Zk9cblBQcE5Gb1BUWGd2M3FDcW5sTEhyR3pNPVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwKICAiY2xpZW50X2VtYWlsIjogImltYWdlLXB1bGxpbmdAYXV0aGVudGljYXRlZC1pbWFnZS1wdWxsaW5nLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwKICAiY2xpZW50X2lkIjogIjExMzc5NzkxNDUzMDA
-3MzI3ODcxMiIsCiAgImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKICAidG9rZW5fdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsCiAgImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAogICJjbGllbnRfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9yb2JvdC92MS9tZXRhZGF0YS94NTA5L2ltYWdlLXB1bGxpbmclNDBhdXRoZW50aWNhdGVkLWltYWdlLXB1bGxpbmcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iCn0=`,
- }: "_json_key:{\n \"type\": \"service_account\",\n \"project_id\": \"authenticated-image-pulling\",\n \"private_key_id\": \"b9f2a664aa9b20484cc1586063fefda19224ac3b\",\n \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7SHnKTEEiYLjf\\nJfAPGmJ3wrBceI50JKLqKmFXNQ/tDXbQ+h9aYxjWIL8Dx0Je74mZ/KMnWgXF5KZS\\noA6KnIO9b/RcSeWeiItRzI3/YXV+O6CcrjJIyxjqVjnfW2i3sa37t9A9TFdlfrrn\\n4zRJb9ixyMX4bLtqFGvB03NIitA3sVZ588koQAfh3JhaBegMj+Z4RbJ4heiBQT03\\nvUo5bEaPeT9DMzlwseaPWgrt6N0OUDcAE9xlcIzMu253Pn/K82HZrtLxjGvRHMUx\\nx4f8pJxfCxxBSwgSNF+w9jdmtvoL0Fa7dgnpRe86VD66z3Yzrj4yKEtjshKdyyUd\\nIyqXh7RRAgMBAAECggEAOzsdwZxCUVQTxAdkl/I5SDUbv/Mk4pifqb2DkagnhEpo\\n1Ij2l4iV10r9/nzrgcjyVPAwzYZMIx1AeQtD7hS4GZapyvJYG76FiXZPRoCVPzou\\nfr8dCiapl5tzrC9lvAsGwoCM7IYTcfcVt7cE12D3QKsF6Z7B2zfgKKnuYPf+CE6T\\ncM0y0h+XE/d0DoHDhW/zaMrXHj8Toweuytkbbs4f/9Fj9PnSgDOYPwlalVTr+FQa\\nJRwVjVlXpFAQmx3BrwnkZt3CiWWiF3d+Hi9EtUbtVrW1b6g+RQOIbqamr+8bRndX\\n6VgqBAkJZ8RVydxUP0d11GjuOPDxBnHBnc4QokIrEQKBgQD1CeicudhWtg4+gSxb\\nzejxtV1N41mducBzo2jyoWGo3PT8wrBO/yQE34qOVJ/id+8I8hZ4oIhu+JA00s6g\\nTnIq+v/d/TEjY81nkZiCkmRPWbXxaYtxR21KPXrLNNQJKkm8tdyXyPql8MoyGfCW\\n2viPJKNb6HZnv9CyjdJ9g2LDnQKBgQDDqSvyDmheb923Ioz4legMR+m9glXUgSKg\\nEsfYemRfmNWB+C7vaIyURmY5NyMxfBVWswWFWKaxc+I+bqsflzzVYtZp18MGjsMD\\nfeefAX6BZMsUt7Bl7Z9VJ85ntEdqACLpZ+Z/3tIRVugCWZQ1hknlGkGT024JEE++\\nNyH1g3d3RQKBgQCRv1wJZI0mPlFIokKFNHua0Tp3KoRSSXsMDSVOM+lHrG1XrmF6\\nC04cS+447GLRLG8UThJJm4qrHtN/Z+gY96/2mqb4HjJND37MXJBvEa3yeLS8q/+R\\n2F9MKjdQiNKZxPpo8W8NJTDY5NkPZdhxkjsHwU4dS66p1TDIE40gtLZZDQKBgFjW\\nKrnQiNq39/b6nP8RMTbCQAJndwjxSQNdA5fqmkA9aFOGl+jjk1CPVkKMIlKJgDbJ\\nOax9v9G6R/MI1HGXfWt1YNzVthr4HtsrA0tSplmhpgNWE6Vz6nADjtfPJs2eGjvX\\njPRp+v8ccmL+wSg8PLjk3vl7ee5rlYlMBwMuGcPxAoGAednxbW1RLmVnlAiHLu/L\\nlmfAwDWmEiI0Ug+PLnoOvO5tQ5d4W1/xEN8lP4qkspkffMQnNh4SYGEeBT32ZqCT\\nJRgf0Xjoyvvup9xXjMkXrpY/yc1zfqTZC0MO9/1Uc1bRGdZ2dy3lR5NWap7OXyfO\\nPPpNFoPTXgv3qCqnlLHrGzM=\\n-----END PRIVATE KEY-----\\n\",\n \"client_email\": \"image-pulling@authenticated-image-pulling.iam.gserviceaccount.com\",\n \"client_id\": \"113797914530073278712\",\n \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n \"token_uri\": \"https://accounts.google.com/o/oauth2/token\",\n \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/image-pulling%40authenticated-image-pulling.iam.gserviceaccount.com\"\n}",
-
- // Errors
- // Auth isn't `username:password` format.
- dockerAuth{
- Auth: "dGhpc2lzYXN0cmluZ3dpdGhvdXRhbnljb2xvbg==",
- }: "",
- // Invalid base64
- dockerAuth{
- Auth: "asda42asd214ASDKqwwq==",
- }: "",
- }
-
- ctx := context.Background()
- for input, expected := range tests {
- username, password, encoded := parseBasicAuth(ctx.Logger(), input)
-
- if expected == "" {
- if encoded != "" {
- t.Errorf("expected an error, got: username=%s, password=%s, encoded=%s", username, password, encoded)
- }
- continue
- }
-
- if diff := cmp.Diff(expected, username+":"+password); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", input, diff)
- }
- }
-}
-
-func Test_ParseAuthenticateHeader(t *testing.T) {
- tests := map[string]map[string]string{
- `Bearer realm="https://auth.docker.io/token",service="registry.docker.io"`: {
- "scheme": "Bearer",
- "realm": "https://auth.docker.io/token",
- "service": "registry.docker.io",
- },
- `Bearer realm="https://ghcr.io/token",service="ghcr.io",scope="repository:user/image:pull"`: {
- "scheme": "Bearer",
- "realm": "https://ghcr.io/token",
- "service": "ghcr.io",
- "scope": "repository:user/image:pull",
- },
- `Bearer realm="https://artifactory.example.com:443/artifactory/api/docker/docker-repo/v2/token",service="artifactory.example.com:443"`: {
- "scheme": "Bearer",
- "realm": "https://artifactory.example.com:443/artifactory/api/docker/docker-repo/v2/token",
- "service": "artifactory.example.com:443",
- },
- }
-
- for input, expected := range tests {
- actual, err := parseAuthenticateHeader(input)
- if err != nil {
- t.Errorf("failed to parse www-authenticate header: %v", err)
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", input, diff)
- }
- }
-}
diff --git a/pkg/detectors/dockerhub/v1/dockerhub.go b/pkg/detectors/dockerhub/v1/dockerhub.go
deleted file mode 100644
index cc27eed489d4..000000000000
--- a/pkg/detectors/dockerhub/v1/dockerhub.go
+++ /dev/null
@@ -1,189 +0,0 @@
-package dockerhub
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- "github.com/golang-jwt/jwt/v5"
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-func (s Scanner) Version() int { return 1 }
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- // Can use email or username for login.
- usernamePat = regexp.MustCompile(detectors.PrefixRegex([]string{"docker"}) + `(?im)(?:user|usr|-u|id)\S{0,40}?[:=\s]{1,3}[ '"=]?([a-zA-Z0-9]{4,40})\b`)
- emailPat = regexp.MustCompile(detectors.PrefixRegex([]string{"docker"}) + common.EmailPattern)
-
- // Can use password or personal access token (PAT) for login, but this scanner will only check for PATs.
- accessTokenPat = regexp.MustCompile(detectors.PrefixRegex([]string{"docker"}) + `\b([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\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{"docker"}
-}
-
-// FromData will find and optionally verify Dockerhub secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- // Deduplicate results.
- tokens := make(map[string]struct{})
- for _, matches := range accessTokenPat.FindAllStringSubmatch(dataStr, -1) {
- tokens[matches[1]] = struct{}{}
- }
- if len(tokens) == 0 {
- return
- }
- usernames := make(map[string]struct{})
- for _, matches := range usernamePat.FindAllStringSubmatch(dataStr, -1) {
- usernames[matches[1]] = struct{}{}
- }
- for _, matches := range emailPat.FindAllStringSubmatch(dataStr, -1) {
- usernames[matches[1]] = struct{}{}
- }
-
- // Process results.
- for token := range tokens {
- s1 := detectors.Result{
- DetectorType: s.Type(),
- Raw: []byte(token),
- }
-
- for username := range usernames {
- s1.RawV2 = []byte(fmt.Sprintf("%s:%s", username, token))
-
- if verify {
- if s.client == nil {
- s.client = common.SaneHttpClient()
- }
-
- isVerified, extraData, verificationErr := s.verifyMatch(ctx, username, token)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr)
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{
- "username": username,
- "pat": token,
- }
- }
- }
-
- results = append(results, s1)
-
- if s1.Verified {
- break
- }
- }
-
- // PAT matches without usernames cannot be verified but might still be useful.
- if len(usernames) == 0 {
- results = append(results, s1)
- }
- }
- return
-}
-
-func (s Scanner) verifyMatch(ctx context.Context, username string, password string) (bool, map[string]string, error) {
- payload := strings.NewReader(fmt.Sprintf(`{"username": "%s", "password": "%s"}`, username, password))
-
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://hub.docker.com/v2/users/login", payload)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- res, err := s.client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer res.Body.Close()
- body, err := io.ReadAll(res.Body)
- if err != nil {
- return false, nil, err
- }
-
- if res.StatusCode == http.StatusOK {
- var tokenRes tokenResponse
- if err := json.Unmarshal(body, &tokenRes); (err != nil || tokenRes == tokenResponse{}) {
- return false, nil, err
- }
-
- parser := jwt.NewParser()
- token, _, err := parser.ParseUnverified(tokenRes.Token, &hubJwtClaims{})
- if err != nil {
- return true, nil, err
- }
-
- if claims, ok := token.Claims.(*hubJwtClaims); ok {
- extraData := map[string]string{
- "hub_username": username,
- "hub_email": claims.HubClaims.Email,
- "hub_scope": claims.Scope,
- }
- return true, extraData, nil
- }
- return true, nil, nil
- } else if res.StatusCode == http.StatusUnauthorized {
- // Valid credentials can still return a 401 status code if 2FA is enabled
- var mfaRes mfaRequiredResponse
- if err := json.Unmarshal(body, &mfaRes); err != nil || mfaRes.MfaToken == "" {
- return false, nil, nil
- }
-
- extraData := map[string]string{
- "hub_username": username,
- "2fa_required": "true",
- }
- return true, extraData, nil
- } else {
- return false, nil, fmt.Errorf("unexpected response status %d", res.StatusCode)
- }
-}
-
-type tokenResponse struct {
- Token string `json:"token"`
-}
-
-type userClaims struct {
- Username string `json:"username"`
- Email string `json:"email"`
-}
-
-type hubJwtClaims struct {
- Scope string `json:"scope"`
- HubClaims userClaims `json:"https://hub.docker.com"` // not sure why this is a key, further investigation required.
- jwt.RegisteredClaims
-}
-
-type mfaRequiredResponse struct {
- MfaToken string `json:"login_2fa_token"`
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dockerhub
-}
-
-func (s Scanner) Description() string {
- return "Docker is a platform used to develop, ship, and run applications. Docker access tokens can be used to authenticate and interact with Docker services."
-}
diff --git a/pkg/detectors/dockerhub/v1/dockerhub_integration_test.go b/pkg/detectors/dockerhub/v1/dockerhub_integration_test.go
deleted file mode 100644
index 5f3526fc0762..000000000000
--- a/pkg/detectors/dockerhub/v1/dockerhub_integration_test.go
+++ /dev/null
@@ -1,141 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dockerhub
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDockerhub_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- username := testSecrets.MustGetField("DOCKERHUB_USERNAME")
- email := testSecrets.MustGetField("DOCKERHUB_EMAIL")
- pat := testSecrets.MustGetField("DOCKERHUB_PAT")
- inactivePat := testSecrets.MustGetField("DOCKERHUB_INACTIVE_PAT")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("docker login -u %s -p %s", username, pat)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dockerhub,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified (email)",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("docker login -u %s -p %s", email, pat)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dockerhub,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("docker login -u %s -p %s", username, inactivePat)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dockerhub,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dockerhub.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].RawV2 = nil
- got[i].ExtraData = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Dockerhub.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dockerhub/v1/dockerhub_test.go b/pkg/detectors/dockerhub/v1/dockerhub_test.go
deleted file mode 100644
index 6d4376e9363c..000000000000
--- a/pkg/detectors/dockerhub/v1/dockerhub_test.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package dockerhub
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- in: ""
- api_version: v1
- secret: ""
- base_url: "https://api.example.com/$api_version/examples"
- response_code: 200
- docker:
- user: rRwOdIJpY90QrIzOXO95d3hlSzRk5Z9a
- docker_email: "docker-test@dockerhub.com"
- docker_token: "9jyxkwvk-rjnp-7eo1-1gtc-ruj6rqmiyapo"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secrets = []string{
- "rRwOdIJpY90QrIzOXO95d3hlSzRk5Z9a:9jyxkwvk-rjnp-7eo1-1gtc-ruj6rqmiyapo",
- "docker-test@dockerhub.com:9jyxkwvk-rjnp-7eo1-1gtc-ruj6rqmiyapo",
- }
-)
-
-func TestDockerHub_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dockerhub/v2/dockerhub.go b/pkg/detectors/dockerhub/v2/dockerhub.go
deleted file mode 100644
index 1ddf609fc542..000000000000
--- a/pkg/detectors/dockerhub/v2/dockerhub.go
+++ /dev/null
@@ -1,189 +0,0 @@
-package dockerhub
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- "github.com/golang-jwt/jwt/v5"
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-func (s Scanner) Version() int { return 2 }
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- // Can use email or username for login.
- usernamePat = regexp.MustCompile(`(?im)(?:user|usr|-u|id)\S{0,40}?[:=\s]{1,3}[ '"=]?([a-zA-Z0-9]{4,40})\b`)
- emailPat = regexp.MustCompile(common.EmailPattern)
-
- // Can use password or personal/organization access token (PAT/OAT) for login, but this scanner will only check for PATs and OATs.
- accessTokenPat = regexp.MustCompile(`\b(dckr_pat_[a-zA-Z0-9_-]{27}|dckr_oat_[a-zA-Z0-9_-]{32})(?:[^a-zA-Z0-9_-]|\z)`)
-)
-
-// 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{"docker", "dckr_pat_", "dckr_oat_"}
-}
-
-// FromData will find and optionally verify Dockerhub secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- // Deduplicate results.
- tokens := make(map[string]struct{})
- for _, matches := range accessTokenPat.FindAllStringSubmatch(dataStr, -1) {
- tokens[matches[1]] = struct{}{}
- }
- if len(tokens) == 0 {
- return
- }
- usernames := make(map[string]struct{})
- for _, matches := range usernamePat.FindAllStringSubmatch(dataStr, -1) {
- usernames[matches[1]] = struct{}{}
- }
- for _, matches := range emailPat.FindAllStringSubmatch(dataStr, -1) {
- usernames[matches[1]] = struct{}{}
- }
-
- // Process results.
- for token := range tokens {
- s1 := detectors.Result{
- DetectorType: s.Type(),
- Raw: []byte(token),
- }
-
- for username := range usernames {
- s1.RawV2 = []byte(fmt.Sprintf("%s:%s", username, token))
-
- if verify {
- if s.client == nil {
- s.client = common.SaneHttpClient()
- }
-
- isVerified, extraData, verificationErr := s.verifyMatch(ctx, username, token)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr)
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{
- "username": username,
- "pat": token,
- }
- }
- }
-
- results = append(results, s1)
-
- if s1.Verified {
- break
- }
- }
-
- // PAT matches without usernames cannot be verified but might still be useful.
- if len(usernames) == 0 {
- results = append(results, s1)
- }
- }
- return
-}
-
-func (s Scanner) verifyMatch(ctx context.Context, username string, password string) (bool, map[string]string, error) {
- payload := strings.NewReader(fmt.Sprintf(`{"identifier": "%s", "secret": "%s"}`, username, password))
-
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://hub.docker.com/v2/auth/token", payload)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- res, err := s.client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer res.Body.Close()
- body, err := io.ReadAll(res.Body)
- if err != nil {
- return false, nil, err
- }
-
- if res.StatusCode == http.StatusOK {
- var tokenRes tokenResponse
- if err := json.Unmarshal(body, &tokenRes); (err != nil || tokenRes == tokenResponse{}) {
- return false, nil, err
- }
-
- parser := jwt.NewParser()
- token, _, err := parser.ParseUnverified(tokenRes.Token, &hubJwtClaims{})
- if err != nil {
- return true, nil, err
- }
-
- if claims, ok := token.Claims.(*hubJwtClaims); ok {
- extraData := map[string]string{
- "hub_username": username,
- "hub_email": claims.HubClaims.Email,
- "hub_scope": claims.Scope,
- }
- return true, extraData, nil
- }
- return true, nil, nil
- } else if res.StatusCode == http.StatusUnauthorized {
- // Valid credentials can still return a 401 status code if 2FA is enabled
- var mfaRes mfaRequiredResponse
- if err := json.Unmarshal(body, &mfaRes); err != nil || mfaRes.MfaToken == "" {
- return false, nil, nil
- }
-
- extraData := map[string]string{
- "hub_username": username,
- "2fa_required": "true",
- }
- return true, extraData, nil
- } else {
- return false, nil, fmt.Errorf("unexpected response status %d", res.StatusCode)
- }
-}
-
-type tokenResponse struct {
- Token string `json:"access_token"`
-}
-
-type userClaims struct {
- Username string `json:"username"`
- Email string `json:"email"`
-}
-
-type hubJwtClaims struct {
- Scope string `json:"scope"`
- HubClaims userClaims `json:"https://hub.docker.com"` // not sure why this is a key, further investigation required.
- jwt.RegisteredClaims
-}
-
-type mfaRequiredResponse struct {
- MfaToken string `json:"login_2fa_token"`
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dockerhub
-}
-
-func (s Scanner) Description() string {
- return "Dockerhub is a cloud-based repository in which Docker users and partners create, test, store and distribute container images. Dockerhub personal access tokens (PATs) can be used to access and manage these container images."
-}
diff --git a/pkg/detectors/dockerhub/v2/dockerhub_integration_test.go b/pkg/detectors/dockerhub/v2/dockerhub_integration_test.go
deleted file mode 100644
index 9b59e257058e..000000000000
--- a/pkg/detectors/dockerhub/v2/dockerhub_integration_test.go
+++ /dev/null
@@ -1,150 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dockerhub
-
-import (
- "context"
- "fmt"
- "strings"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDockerhub_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- username := testSecrets.MustGetField("DOCKERHUB_USERNAME")
- email := testSecrets.MustGetField("DOCKERHUB_EMAIL")
- pat := testSecrets.MustGetField("DOCKERHUB_PAT")
- inactivePat := testSecrets.MustGetField("DOCKERHUB_INACTIVE_PAT")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("docker login -u %s -p %s", username, pat)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dockerhub,
- Verified: true,
- AnalysisInfo: map[string]string{
- "username": username,
- "pat": pat,
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified (email)",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("docker login -u %s -p %s", email, pat)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dockerhub,
- Verified: true,
- AnalysisInfo: map[string]string{
- "username": strings.Split(email, "-")[0],
- "pat": pat,
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("docker login -u %s -p %s", username, inactivePat)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dockerhub,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dockerhub.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].RawV2 = nil
- got[i].ExtraData = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Dockerhub.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dockerhub/v2/dockerhub_test.go b/pkg/detectors/dockerhub/v2/dockerhub_test.go
deleted file mode 100644
index e1d88840904c..000000000000
--- a/pkg/detectors/dockerhub/v2/dockerhub_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package dockerhub
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: ""
- in: ""
- api_version: v1
- secret: ""
- base_url: "https://api.example.com/$api_version/examples"
- response_code: 200
- docker:
- user: rRwOdIJpY90QrIzOXO95d3hlSzRk5Z9a
- docker_email: "docker-test@dockerhub.com"
- docker_token: "dckr_pat_dlndn9l2JLhWvbdyP3blEZw_j7d"
- docker_org_token: "dckr_oat_7bA9zRt5-JqX3vP0l_MnY8sK2wE-dF6h"
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secrets = []string{
- "rRwOdIJpY90QrIzOXO95d3hlSzRk5Z9a:dckr_pat_dlndn9l2JLhWvbdyP3blEZw_j7d",
- "docker-test@dockerhub.com:dckr_pat_dlndn9l2JLhWvbdyP3blEZw_j7d",
- "rRwOdIJpY90QrIzOXO95d3hlSzRk5Z9a:dckr_oat_7bA9zRt5-JqX3vP0l_MnY8sK2wE-dF6h",
- "docker-test@dockerhub.com:dckr_oat_7bA9zRt5-JqX3vP0l_MnY8sK2wE-dF6h",
- }
-)
-
-func TestDockerHub_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/docparser/docparser.go b/pkg/detectors/docparser/docparser.go
deleted file mode 100644
index 8d4fb4ef801d..000000000000
--- a/pkg/detectors/docparser/docparser.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package docparser
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"docparser"}) + `\b([a-f0-9]{40})\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{"docparser"}
-}
-
-// FromData will find and optionally verify Docparser secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Docparser,
- Raw: []byte(resMatch),
- }
-
- if verify {
- url := fmt.Sprintf("https://api.docparser.com/v1/parsers?api_key=%s", resMatch)
- req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
-
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Docparser
-}
-
-func (s Scanner) Description() string {
- return "Docparser is a document processing service that extracts data from PDFs and scanned documents. Docparser API keys can be used to access and manipulate this data."
-}
diff --git a/pkg/detectors/docparser/docparser_integration_test.go b/pkg/detectors/docparser/docparser_integration_test.go
deleted file mode 100644
index 0bdc0411885d..000000000000
--- a/pkg/detectors/docparser/docparser_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package docparser
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDocparser_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DOCPARSER")
- inactiveSecret := testSecrets.MustGetField("DOCPARSER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a docparser secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Docparser,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a docparser secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Docparser,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Docparser.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Docparser.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/docparser/docparser_test.go b/pkg/detectors/docparser/docparser_test.go
deleted file mode 100644
index cded399b9373..000000000000
--- a/pkg/detectors/docparser/docparser_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package docparser
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- api_version: v1
- docparser_secret: "1761d026b1202108b5f9ecd28d1ecae826b0aee8"
- base_url: "https://api.example.com/$api_version/example/api_key=$docparser_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "1761d026b1202108b5f9ecd28d1ecae826b0aee8"
-)
-
-func TestDocParser_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/documo/documo.go b/pkg/detectors/documo/documo.go
deleted file mode 100644
index 73d0355de535..000000000000
--- a/pkg/detectors/documo/documo.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package documo
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(ey[a-zA-Z0-9]{34}.ey[a-zA-Z0-9]{154}.[a-zA-Z0-9_-]{43})\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{"documo"}
-}
-
-// FromData will find and optionally verify Documo secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Documo,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.documo.com/v1/me", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Documo
-}
-
-func (s Scanner) Description() string {
- return "A service for creating and modifying documents. API keys can create read update and delete documents."
-}
diff --git a/pkg/detectors/documo/documo_integration_test.go b/pkg/detectors/documo/documo_integration_test.go
deleted file mode 100644
index 29435856ccef..000000000000
--- a/pkg/detectors/documo/documo_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package documo
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDocumo_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DOCUMO")
- inactiveSecret := testSecrets.MustGetField("DOCUMO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a documo secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Documo,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a documo secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Documo,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Documo.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Documo.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/documo/documo_test.go b/pkg/detectors/documo/documo_test.go
deleted file mode 100644
index 1008a59f5495..000000000000
--- a/pkg/detectors/documo/documo_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package documo
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Documo Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- in: "Path"
- api_version: v1
- secret: "eyS9YqgD6TgdQ943G8S3aaiz26m2fTN9rcPbpeyts0jBEFd43hEFfr9pC7voqvLsbEi7Px4TbMToCVrstQRe8r2kltKGWyChYCT1Iruo6p3g3PyqZaZ1gOSbjeXz8zARUHZkXo7XR86kape65HLXj59yCNIlW5bvebJYbIAjjgGAAmXVgzldvNv8Zs08KIS5y62QJSNcnipFQbnxA8z6TUMl0F600MJhqEILWo19GaGjw"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "eyS9YqgD6TgdQ943G8S3aaiz26m2fTN9rcPbpeyts0jBEFd43hEFfr9pC7voqvLsbEi7Px4TbMToCVrstQRe8r2kltKGWyChYCT1Iruo6p3g3PyqZaZ1gOSbjeXz8zARUHZkXo7XR86kape65HLXj59yCNIlW5bvebJYbIAjjgGAAmXVgzldvNv8Zs08KIS5y62QJSNcnipFQbnxA8z6TUMl0F600MJhqEILWo19GaGjw"
-)
-
-func TestDocumo_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/docusign/docusign.go b/pkg/detectors/docusign/docusign.go
deleted file mode 100644
index 6f2047835884..000000000000
--- a/pkg/detectors/docusign/docusign.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package docusign
-
-import (
- "context"
- "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- "github.com/go-errors/errors"
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-type Response struct {
- AccessToken string `json:"access_token"`
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"integration", "id"}) + common.UUIDPattern)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"secret"}) + common.UUIDPattern)
-)
-
-// 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{"docusign"}
-}
-
-// FromData will find and optionally verify Docusign secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, idMatch := range idMatches {
- resIDMatch := strings.TrimSpace(idMatch[1])
-
- for _, secretMatch := range secretMatches {
- resSecretMatch := strings.TrimSpace(secretMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Docusign,
- Raw: []byte(resIDMatch),
- Redacted: resIDMatch,
- RawV2: []byte(resIDMatch + resSecretMatch),
- }
-
- // Verify client id and secret pair by using an *undocumented* client_credentials grant type on the oauth2 endpoint.
- // If verifier breaks in the future, confirm that the oauth2 endpoint is still accepting the client_credentials grant type.
- if verify {
- req, err := http.NewRequestWithContext(ctx, "POST", "https://account-d.docusign.com/oauth/token?grant_type=client_credentials", nil)
- if err != nil {
- continue
- }
-
- encodedCredentials := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", resIDMatch, resSecretMatch)))
-
- req.Header.Add("Accept", "application/vnd.docusign+json; version=3")
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedCredentials))
- res, err := client.Do(req)
-
- if err != nil {
- return nil, errors.WrapPrefix(err, "Error making request", 0)
- }
-
- verifiedBodyResponse, err := common.ResponseContainsSubstring(res.Body, "ey")
- res.Body.Close()
-
- if err != nil {
- return nil, err
- }
-
- if err == nil {
- if res.StatusCode >= 200 && res.StatusCode < 300 && verifiedBodyResponse {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Docusign
-}
-
-func (s Scanner) Description() string {
- return "Docusign is an electronic signature and digital transaction management service. Docusign credentials can be used to access and manage digital transactions and documents."
-}
diff --git a/pkg/detectors/docusign/docusign_integration_test.go b/pkg/detectors/docusign/docusign_integration_test.go
deleted file mode 100644
index b3911ae31cdc..000000000000
--- a/pkg/detectors/docusign/docusign_integration_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package docusign
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDocusign_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- integrationKey := testSecrets.MustGetField("DOCUSIGN_INTEGRATION_KEY_ACTIVE")
- activeSecret := testSecrets.MustGetField("DOCUSIGN_SECRET_ACTIVE")
- inactiveSecret := testSecrets.MustGetField("DOCUSIGN_SECRET_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a docusign id %s and secret %s within", integrationKey, activeSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Docusign,
- Verified: true,
- RawV2: []byte(integrationKey + activeSecret),
- Redacted: integrationKey,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a docusign id %s and secret %s within", integrationKey, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Docusign,
- Verified: false,
- RawV2: []byte(integrationKey + inactiveSecret),
- Redacted: integrationKey,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Docusign.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Docusign.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/docusign/docusign_test.go b/pkg/detectors/docusign/docusign_test.go
deleted file mode 100644
index faf8ecf637d9..000000000000
--- a/pkg/detectors/docusign/docusign_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package docusign
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- in: "Path"
- api_version: v1
- docusign_id: "03f36108-730e-9061-ad3f-b77c910b2559"
- docusign_secret: "212904c1-60fc-09b2-d615-1849cd748bf4"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "03f36108-730e-9061-ad3f-b77c910b2559212904c1-60fc-09b2-d615-1849cd748bf4"
-)
-
-func TestDocsign_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/doppler/doppler.go b/pkg/detectors/doppler/doppler.go
deleted file mode 100644
index 4ed8dad54625..000000000000
--- a/pkg/detectors/doppler/doppler.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package doppler
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type response struct {
- Name string `json:"name"`
- Type string `json:"type"`
- Workplace struct {
- Name string `json:"name"`
- } `json:"workplace"`
-}
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- //keyPat = regexp.MustCompile(`\b(dp\.pt\.[a-zA-Z0-9]{43})\b`)
- keyPat = regexp.MustCompile(`\b(dp\.(?:ct|pt|st(?:\.[a-z0-9\-_]{2,35})?|sa|scim|audit)\.[a-zA-Z0-9]{40,44})\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{
- "dp.ct.",
- "dp.pt.",
- "dp.st",
- "dp.sa.",
- "dp.scim.",
- "dp.audit.",
- }
-}
-
-// FromData will find and optionally verify Doppler secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Doppler,
- Raw: []byte(resMatch),
- ExtraData: map[string]string{},
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.doppler.com/v3/me", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- var r response
- if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
- s1.SetVerificationError(err, resMatch)
- continue
- }
- if r.Type != "" {
- s1.ExtraData["key type"] = r.Type
- }
- if r.Workplace.Name != "" {
- s1.ExtraData["workplace"] = r.Workplace.Name
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Doppler
-}
-
-func (s Scanner) Description() string {
- return "Doppler is a secrets management platform that allows teams to manage and secure environment variables and secrets. Doppler tokens can be used to access and manage these secrets."
-}
diff --git a/pkg/detectors/doppler/doppler_integration_test.go b/pkg/detectors/doppler/doppler_integration_test.go
deleted file mode 100644
index 8e8a88893a56..000000000000
--- a/pkg/detectors/doppler/doppler_integration_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package doppler
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDoppler_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DOPPLER")
- inactiveSecret := testSecrets.MustGetField("DOPPLER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a doppler secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Doppler,
- Verified: true,
- ExtraData: map[string]string{
- "key type": "personal",
- "workplace": "test",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a doppler secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Doppler,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Doppler.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Doppler.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/doppler/doppler_test.go b/pkg/detectors/doppler/doppler_test.go
deleted file mode 100644
index eb48e2c12fb7..000000000000
--- a/pkg/detectors/doppler/doppler_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package doppler
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Documo Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Path"
- api_version: v1
- doppler_secret: "dp.ct.5KE9aLrlMoprKwgigGZl1zJOOMQDcYPTWoTPujmF5Tm3"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "dp.ct.5KE9aLrlMoprKwgigGZl1zJOOMQDcYPTWoTPujmF5Tm3"
-)
-
-func TestDoppler_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dotdigital/dotdigital.go b/pkg/detectors/dotdigital/dotdigital.go
deleted file mode 100644
index 6fe5b4197192..000000000000
--- a/pkg/detectors/dotdigital/dotdigital.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package dotdigital
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- emailPat = regexp.MustCompile(`\b(apiuser-[a-z0-9]{12}@apiconnector.com)\b`)
- passPat = regexp.MustCompile(detectors.PrefixRegex([]string{"pw", "pass"}) + `\b([a-zA-Z0-9\S]{8,24})\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{"@apiconnector.com"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Dotdigital secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueEmails, uniquePasswords = make(map[string]struct{}), make(map[string]struct{})
-
- for _, matches := range emailPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueEmails[matches[1]] = struct{}{}
- }
- for _, matches := range passPat.FindAllStringSubmatch(dataStr, -1) {
- uniquePasswords[matches[1]] = struct{}{}
- }
-
- for email := range uniqueEmails {
- for password := range uniquePasswords {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Dotdigital,
- Raw: []byte(email),
- RawV2: []byte(email + password),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyMatch(ctx, client, email, password)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
-
- if s1.Verified {
- // Once the email is verified, we can stop checking other passwords for it.
- break
- }
- }
- }
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, email, pass string) (bool, error) {
- // Reference: https://developer.dotdigital.com/reference/get-account-information
-
- timeout := 10 * time.Second
- client.Timeout = timeout
- url := "https://r1-api.dotdigital.com/v2/account-info"
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.SetBasicAuth(email, pass)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dotdigital
-}
-
-func (s Scanner) Description() string {
- return "Dotdigital is an email marketing automation platform. API keys can be used to access and manage email campaigns and related data."
-}
diff --git a/pkg/detectors/dotdigital/dotdigital_integration_test.go b/pkg/detectors/dotdigital/dotdigital_integration_test.go
deleted file mode 100644
index 1a9a409331d0..000000000000
--- a/pkg/detectors/dotdigital/dotdigital_integration_test.go
+++ /dev/null
@@ -1,133 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dotdigital
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDotdigital_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- email := testSecrets.MustGetField("DOTDIGITAL_EMAIL")
- password := testSecrets.MustGetField("DOTDIGITAL_PASSWORD")
- inactivePassword := testSecrets.MustGetField("DOTDIGITAL_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dotdigital user %s within dotdigital pass %s", email, password)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dotdigital,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dotdigital user %s within dotdigital pass %s but not valid ", email, inactivePassword)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dotdigital,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dotdigital.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", "primarySecret")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Dotdigital.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dotdigital/dotdigital_test.go b/pkg/detectors/dotdigital/dotdigital_test.go
deleted file mode 100644
index f408ccc51c87..000000000000
--- a/pkg/detectors/dotdigital/dotdigital_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package dotdigital
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
-
- api:
- auth_type: "Basic"
- dotdigital_email: "apiuser-trq6zw9mmdlt@apiconnector.com"
- dotdigital_password: "N{w44mqa'2si(zY8"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - The above credentials should only be used in a secure environment.
- `
- secrets = []string{"apiuser-trq6zw9mmdlt@apiconnector.comN{w44mqa'2si(zY8"}
-)
-
-func TestDotdigital_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dovico/dovico.go b/pkg/detectors/dovico/dovico.go
deleted file mode 100644
index f1282cb3b367..000000000000
--- a/pkg/detectors/dovico/dovico.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package dovico
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dovico"}) + `\b([0-9a-z]{32}\.[0-9a-z]{1,}\b)`)
- userPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dovico"}) + `\b([0-9a-z]{32}\.[0-9a-z]{1,}\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{"dovico"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Dovico secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueKeys := make(map[string]struct{})
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- uniqueUserKeys := make(map[string]struct{})
- for _, matches := range userPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueUserKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- for userKey := range uniqueUserKeys {
- if key == userKey {
- continue // Skip if ID and secret are the same.
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Dovico,
- Raw: []byte(key),
- RawV2: []byte(fmt.Sprintf("%s:%s", key, userKey)),
- }
-
- if verify {
- client := s.getClient()
- isVerified, err := verifyMatch(ctx, client, key, userKey)
- s1.Verified = isVerified
- s1.SetVerificationError(err, key, userKey)
- }
-
- results = append(results, s1)
-
- // Credentials have 1:1 mapping so we can stop checking other user keys once it is verified
- if s1.Verified {
- break
- }
- }
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, key, user string) (bool, error) {
- // Reference: https://timesheet.dovico.com/developer/API_doc/#t=API_Overview.html
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.dovico.com/employees/?version=7", http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf(`WRAP access_token="client=%s&user_token=%s"`, key, user))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dovico
-}
-
-func (s Scanner) Description() string {
- return "Dovico is a time tracking and project management service. Dovico keys can be used to access and manage time tracking and project data."
-}
diff --git a/pkg/detectors/dovico/dovico_integration_test.go b/pkg/detectors/dovico/dovico_integration_test.go
deleted file mode 100644
index 17ddedc462c3..000000000000
--- a/pkg/detectors/dovico/dovico_integration_test.go
+++ /dev/null
@@ -1,131 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dovico
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDovico_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DOVICO_CLIENT")
- user := testSecrets.MustGetField("DOVICO_USER")
- inactiveSecret := testSecrets.MustGetField("DOVICO_CLIENT_INACTIVE")
- inactiveUser := testSecrets.MustGetField("DOVICO_USER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dovico secret %s within dovico user %s ", secret, user)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dovico,
- Verified: true,
- },
- {
- DetectorType: detectorspb.DetectorType_Dovico,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dovico secret %s within dovico user %s but not valid", inactiveSecret, inactiveUser)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dovico,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Dovico,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dovico.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Dovico.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dovico/dovico_test.go b/pkg/detectors/dovico/dovico_test.go
deleted file mode 100644
index cf941cbf950e..000000000000
--- a/pkg/detectors/dovico/dovico_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package dovico
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Token"
- in: "Header"
- api_version: v1
- dovico_user: "ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4ol"
- dovico_token: "nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6e"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secrets = []string{
- "nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6e:ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4ol",
- "ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4ol:nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6e",
- }
-)
-
-func TestDovico_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dronahq/dronahq.go b/pkg/detectors/dronahq/dronahq.go
deleted file mode 100644
index eb23a8b70c6f..000000000000
--- a/pkg/detectors/dronahq/dronahq.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package dronahq
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dronahq"}) + `\b([a-z0-9]{50})\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{"dronahq"}
-}
-
-// FromData will find and optionally verify DronaHQ secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DronaHQ,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://plugin.api.dronahq.com/users/?tokenkey=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DronaHQ
-}
-
-func (s Scanner) Description() string {
- return "DronaHQ is a platform for building internal tools and applications. DronaHQ keys can be used to access and manage these tools and applications."
-}
diff --git a/pkg/detectors/dronahq/dronahq_integration_test.go b/pkg/detectors/dronahq/dronahq_integration_test.go
deleted file mode 100644
index 87d542fbbf6d..000000000000
--- a/pkg/detectors/dronahq/dronahq_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dronahq
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDronaHQ_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DRONAHQ")
- inactiveSecret := testSecrets.MustGetField("DRONAHQ_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dronahq secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DronaHQ,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dronahq secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DronaHQ,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("DronaHQ.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("DronaHQ.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dronahq/dronahq_test.go b/pkg/detectors/dronahq/dronahq_test.go
deleted file mode 100644
index 9510b2767c07..000000000000
--- a/pkg/detectors/dronahq/dronahq_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package dronahq
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- api_version: v1
- dronahq_secret: ""
- base_url: "https://api.dronahq.com/$api_version/example?tokenkey=5j5jvhn9hm6qojajn61pe1ccqly424lrd0g41vbh6wwscer3pa"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "5j5jvhn9hm6qojajn61pe1ccqly424lrd0g41vbh6wwscer3pa"
-)
-
-func TestDronahq_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/droneci/droneci.go b/pkg/detectors/droneci/droneci.go
deleted file mode 100644
index 0084515806f4..000000000000
--- a/pkg/detectors/droneci/droneci.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package droneci
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"droneci"}) + `\b([a-zA-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"droneci"}
-}
-
-// FromData will find and optionally verify DroneCI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_DroneCI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://cloud.drone.io/api/user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_DroneCI
-}
-
-func (s Scanner) Description() string {
- return "DroneCI is a continuous integration service that automates the testing and deployment of applications. DroneCI tokens can be used to access and control CI/CD pipelines."
-}
diff --git a/pkg/detectors/droneci/droneci_integration_test.go b/pkg/detectors/droneci/droneci_integration_test.go
deleted file mode 100644
index f4538cb1c7ba..000000000000
--- a/pkg/detectors/droneci/droneci_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package droneci
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDroneCI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DRONECI_TOKEN")
- inactiveSecret := testSecrets.MustGetField("DRONECI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a droneci secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DroneCI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a droneci secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_DroneCI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("DroneCI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("DroneCI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/droneci/droneci_test.go b/pkg/detectors/droneci/droneci_test.go
deleted file mode 100644
index d76ca512da2d..000000000000
--- a/pkg/detectors/droneci/droneci_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package droneci
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- api_version: v1
- droneci_secret: "Kf6ZyWFttCZwO9SqEB94opCHaQ5n00WF"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "Kf6ZyWFttCZwO9SqEB94opCHaQ5n00WF"
-)
-
-func TestDroneCI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dropbox/dropbox.go b/pkg/detectors/dropbox/dropbox.go
deleted file mode 100644
index 4568576d55c9..000000000000
--- a/pkg/detectors/dropbox/dropbox.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package dropbox
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dropbox"}) + `\b(sl\.(u\.)?[A-Za-z0-9\-\_]{130,})\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{"dropbox", "sl."}
-}
-
-// FromData will find and optionally verify Dropbox secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matches[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Dropbox,
- Raw: []byte(key),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyDropboxToken(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{"token": key}
- }
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyDropboxToken(ctx context.Context, client *http.Client, key string) (bool, error) {
- // Reference: https://www.dropbox.com/developers/documentation/http/documentation
- url := "https://api.dropboxapi.com/2/users/get_current_account"
-
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusBadRequest:
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, fmt.Errorf("failed to read response body: %w", err)
- }
- body := string(bodyBytes)
-
- if strings.Contains(body, "missing_scope") ||
- strings.Contains(body, "does not have the required scope") {
- return true, nil // The token is valid but lacks the required scope
- }
- if strings.Contains(body, "invalid_access_token") ||
- strings.Contains(body, "expired_access_token") {
- return false, nil // The token is invalid or expired
- }
- return false, fmt.Errorf("unexpected status code: %d, body: %s", res.StatusCode, body)
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dropbox
-}
-
-func (s Scanner) Description() string {
- return "Dropbox is a file hosting service that offers cloud storage, file synchronization, personal cloud, and client software. Dropbox API keys can be used to access and manage files and folders in a Dropbox account."
-}
diff --git a/pkg/detectors/dropbox/dropbox_integration_test.go b/pkg/detectors/dropbox/dropbox_integration_test.go
deleted file mode 100644
index 3b13b813c537..000000000000
--- a/pkg/detectors/dropbox/dropbox_integration_test.go
+++ /dev/null
@@ -1,116 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dropbox
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDropbox_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DROPBOX")
- secretInactive := testSecrets.MustGetField("DROPBOX_INACTIVE")
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dropbox secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dropbox,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dropbox secret %s within", secretInactive)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dropbox,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dropbox.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Dropbox.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dropbox/dropbox_test.go b/pkg/detectors/dropbox/dropbox_test.go
deleted file mode 100644
index 990284e3ce22..000000000000
--- a/pkg/detectors/dropbox/dropbox_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package dropbox
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- api_version: v1
- dropbox_secret: "sl.4ihqlizKRm9J8tJvdBUecLPfYunjh3Nx73cUBGcRKpTFxRny3cYKdaQdzVF_rBIEO9emJaHyRWeM_tm5pYJFTc1TwYjM2fHlhSdhKkzHJjf5dx86fUlaO_eKY9r4ijZ8eD"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "sl.4ihqlizKRm9J8tJvdBUecLPfYunjh3Nx73cUBGcRKpTFxRny3cYKdaQdzVF_rBIEO9emJaHyRWeM_tm5pYJFTc1TwYjM2fHlhSdhKkzHJjf5dx86fUlaO_eKY9r4ijZ8eD"
-)
-
-func TestDropBox_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/duply/duply.go b/pkg/detectors/duply/duply.go
deleted file mode 100644
index c5cb0d65f975..000000000000
--- a/pkg/detectors/duply/duply.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package duply
-
-import (
- "context"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"duply"}) + `\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"duply"}) + `\b([0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7})\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{"duply"}
-}
-
-// FromData will find and optionally verify Duply secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- resIdMatch := strings.TrimSpace(idMatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Duply,
- Raw: []byte(resMatch),
- }
-
- if verify {
- timeout := 10 * time.Second
- client.Timeout = timeout
- req, err := http.NewRequestWithContext(ctx, "GET", "https://gen.duply.co/v1/usage", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.SetBasicAuth(resIdMatch, resMatch)
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Duply
-}
-
-func (s Scanner) Description() string {
- return "An API for generating images. API keys can fetch and create images."
-}
diff --git a/pkg/detectors/duply/duply_integration_test.go b/pkg/detectors/duply/duply_integration_test.go
deleted file mode 100644
index 8c2a7f3306dc..000000000000
--- a/pkg/detectors/duply/duply_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package duply
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDuply_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DUPLY")
- id := testSecrets.MustGetField("DUPLY_USER")
- inactiveSecret := testSecrets.MustGetField("DUPLY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a duply secret %s within duply %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Duply,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a duply secret %s within duply %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Duply,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Duply.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Duply.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/duply/duply_test.go b/pkg/detectors/duply/duply_test.go
deleted file mode 100644
index bb429bbb3d3f..000000000000
--- a/pkg/detectors/duply/duply_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package duply
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- in: "Header"
- api_version: v1
- duply_id: "JN9YXKN-2OB6UTI-VTN7DX8-FIZZM7P"
- duply_secret: "24cc4537-f4ea-b9de-7369-41481c6e914f"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "24cc4537-f4ea-b9de-7369-41481c6e914f"
-)
-
-func TestDuply_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dwolla/dwolla.go b/pkg/detectors/dwolla/dwolla.go
deleted file mode 100644
index d9223a7cbe97..000000000000
--- a/pkg/detectors/dwolla/dwolla.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package dwolla
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dwolla"}) + `\b([a-zA-Z-0-9]{50})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dwolla"}) + `\b([a-zA-Z-0-9]{50})\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{"dwolla"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Dwolla secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueIDs := make(map[string]struct{})
- for _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueIDs[matches[1]] = struct{}{}
- }
-
- uniqueSecrets := make(map[string]struct{})
- for _, matches := range secretPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueSecrets[matches[1]] = struct{}{}
- }
-
- for id := range uniqueIDs {
- for secret := range uniqueSecrets {
- if id == secret {
- continue // Skip if ID and secret are the same.
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Dwolla,
- Raw: []byte(id),
- RawV2: []byte(id + secret),
- }
-
- if verify {
- client := s.getClient()
- isVerified, err := verifyMatch(ctx, client, id, secret)
- s1.Verified = isVerified
- s1.SetVerificationError(err, id, secret)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, id, secret string) (bool, error) {
- data := fmt.Sprintf("%s:%s", id, secret)
- encoded := b64.StdEncoding.EncodeToString([]byte(data))
- payload := strings.NewReader("grant_type=client_credentials")
-
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api-sandbox.dwolla.com/token", payload)
- if err != nil {
- return false, err
- }
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encoded))
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dwolla
-}
-
-func (s Scanner) Description() string {
- return "Dwolla is a payment services provider that allows businesses to send, receive, and facilitate payments. Dwolla API keys can be used to access and manage these payment services."
-}
diff --git a/pkg/detectors/dwolla/dwolla_integration_test.go b/pkg/detectors/dwolla/dwolla_integration_test.go
deleted file mode 100644
index dfedc6a99d16..000000000000
--- a/pkg/detectors/dwolla/dwolla_integration_test.go
+++ /dev/null
@@ -1,141 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dwolla
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDwolla_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- id := testSecrets.MustGetField("DWOLLA_ID")
- secret := testSecrets.MustGetField("DWOLLA_SECRET")
- inactiveSecret := testSecrets.MustGetField("DWOLLA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dwolla secret %s within dwolla id %s but verified", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dwolla,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Dwolla,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dwolla secret %s within dwolla id %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dwolla,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Dwolla,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dwolla.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", "primarySecret")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Dwolla.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dwolla/dwolla_test.go b/pkg/detectors/dwolla/dwolla_test.go
deleted file mode 100644
index cf154799cd3e..000000000000
--- a/pkg/detectors/dwolla/dwolla_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package dwolla
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- in: "Header"
- api_version: v1
- dwolla_id: "MvkLktYDS7PSE0xRMHIYBKrAjXruEk5P1VrJUUGtgspa3KTi6r"
- dwolla_secret: "q3DZbY7iviUpewfCHEpK1I51G8XW63GuLuJyAIEqOFtEB1qlg1"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secrets = []string{
- "MvkLktYDS7PSE0xRMHIYBKrAjXruEk5P1VrJUUGtgspa3KTi6rq3DZbY7iviUpewfCHEpK1I51G8XW63GuLuJyAIEqOFtEB1qlg1",
- "q3DZbY7iviUpewfCHEpK1I51G8XW63GuLuJyAIEqOFtEB1qlg1MvkLktYDS7PSE0xRMHIYBKrAjXruEk5P1VrJUUGtgspa3KTi6r",
- }
-)
-
-func TestDwollaPattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dynalist/dynalist.go b/pkg/detectors/dynalist/dynalist.go
deleted file mode 100644
index 4d9da75ae546..000000000000
--- a/pkg/detectors/dynalist/dynalist.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package dynalist
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dynalist"}) + `\b([a-zA-Z0-9-_]{128})\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{"dynalist"}
-}
-
-// FromData will find and optionally verify Dynalist secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Dynalist,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(fmt.Sprintf(`{"token": "%s"}`, resMatch))
- req, err := http.NewRequestWithContext(ctx, "POST", "https://dynalist.io/api/v1/file/list", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err == nil {
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"_code":"Ok"`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dynalist
-}
-
-func (s Scanner) Description() string {
- return "Dynalist is a web-based outlining app that allows users to create and manage hierarchical lists. Dynalist API tokens can be used to access and manipulate these lists programmatically."
-}
diff --git a/pkg/detectors/dynalist/dynalist_integration_test.go b/pkg/detectors/dynalist/dynalist_integration_test.go
deleted file mode 100644
index 0d4fbb270e56..000000000000
--- a/pkg/detectors/dynalist/dynalist_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dynalist
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDynalist_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DYNALIST")
- inactiveSecret := testSecrets.MustGetField("DYNALIST_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dynalist secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dynalist,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dynalist secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dynalist,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dynalist.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Dynalist.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dynalist/dynalist_test.go b/pkg/detectors/dynalist/dynalist_test.go
deleted file mode 100644
index dcc5f707bb94..000000000000
--- a/pkg/detectors/dynalist/dynalist_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package dynalist
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Token"
- in: "Body"
- api_version: v1
- dynalist_secret: "60l0XYOX_VZrJsbpid4TllHwdek_3NXxgKz_DkO3lrw_B8aHxSov-TOPojCMtBED8q4awfqsMdNcOkCxrmqkbDQW2dJ8lukGTgJZBqfPQKOYujZZBXKZng3SXM-huIRM"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "60l0XYOX_VZrJsbpid4TllHwdek_3NXxgKz_DkO3lrw_B8aHxSov-TOPojCMtBED8q4awfqsMdNcOkCxrmqkbDQW2dJ8lukGTgJZBqfPQKOYujZZBXKZng3SXM-huIRM"
-)
-
-func TestDynalist_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/dyspatch/dyspatch.go b/pkg/detectors/dyspatch/dyspatch.go
deleted file mode 100644
index 670389107fcf..000000000000
--- a/pkg/detectors/dyspatch/dyspatch.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package dyspatch
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"dyspatch"}) + `\b([A-Z0-9]{52})\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{"dyspatch"}
-}
-
-// FromData will find and optionally verify Dyspatch secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Dyspatch,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.dyspatch.io/templates", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.dyspatch.2020.11+json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
- validResponse := strings.Contains(body, "limited_usage") || strings.Contains(body, "data")
-
- if validResponse {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Dyspatch
-}
-
-func (s Scanner) Description() string {
- return "Dyspatch is a platform for managing and sending transactional emails. Dyspatch API keys can be used to access and manage email templates and sending operations."
-}
diff --git a/pkg/detectors/dyspatch/dyspatch_integration_test.go b/pkg/detectors/dyspatch/dyspatch_integration_test.go
deleted file mode 100644
index 16d968a36553..000000000000
--- a/pkg/detectors/dyspatch/dyspatch_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package dyspatch
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDyspatch_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("DYSPATCH_TOKEN")
- inactiveSecret := testSecrets.MustGetField("DYSPATCH_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dyspatch secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dyspatch,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a dyspatch secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Dyspatch,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Dyspatch.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Dyspatch.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/dyspatch/dyspatch_test.go b/pkg/detectors/dyspatch/dyspatch_test.go
deleted file mode 100644
index 754b39bd1329..000000000000
--- a/pkg/detectors/dyspatch/dyspatch_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package dyspatch
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- api_version: v1
- dyspatch_secret: "RLZTXG010RHW7FCSAEX72TPRJS1JU1PU0PVSWFF6HZQOUEVY5MFN"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "RLZTXG010RHW7FCSAEX72TPRJS1JU1PU0PVSWFF6HZQOUEVY5MFN"
-)
-
-func TestDyspatch_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/eagleeyenetworks/eagleeyenetworks.go b/pkg/detectors/eagleeyenetworks/eagleeyenetworks.go
deleted file mode 100644
index 36cea7f9ccce..000000000000
--- a/pkg/detectors/eagleeyenetworks/eagleeyenetworks.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package eagleeyenetworks
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"eagleeyenetworks"}) + `\b([a-zA-Z0-9]{15})\b`)
- email = regexp.MustCompile(detectors.PrefixRegex([]string{"eagleeyenetworks"}) + `\b([a-zA-Z0-9]{3,20}@[a-zA-Z0-9]{2,12}.[a-zA-Z0-9]{2,5})\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{"eagleeyenetworks"}
-}
-
-// FromData will find and optionally verify EagleEyeNetworks secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- emailMatches := email.FindAllStringSubmatch(dataStr, -1)
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, emailMatch := range emailMatches {
-
- resEmailPatMatch := strings.TrimSpace(emailMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_EagleEyeNetworks,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(fmt.Sprintf(`{"username": "%s", "password": "%s"}`, resEmailPatMatch, resMatch))
- req, err := http.NewRequestWithContext(ctx, "POST", "https://login.eagleeyenetworks.com/g/aaa/authenticate", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_EagleEyeNetworks
-}
-
-func (s Scanner) Description() string {
- return "Eagle Eye Networks provides cloud-based video surveillance solutions. The credentials can be used to access and manage surveillance data."
-}
diff --git a/pkg/detectors/eagleeyenetworks/eagleeyenetworks_integration_test.go b/pkg/detectors/eagleeyenetworks/eagleeyenetworks_integration_test.go
deleted file mode 100644
index 8706b4d9ee11..000000000000
--- a/pkg/detectors/eagleeyenetworks/eagleeyenetworks_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package eagleeyenetworks
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEagleEyeNetworks_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("EAGLEEYENETWORKS")
- email := testSecrets.MustGetField("EAGLEEYENETWORKS_USER")
- inactiveSecret := testSecrets.MustGetField("EAGLEEYENETWORKS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a eagleeyenetworks secret %s within eagleeyenetworks %s", secret, email)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EagleEyeNetworks,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a eagleeyenetworks secret %s within eagleeyenetworks %s but not valid", inactiveSecret, email)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EagleEyeNetworks,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("EagleEyeNetworks.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("EagleEyeNetworks.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/eagleeyenetworks/eagleeyenetworks_test.go b/pkg/detectors/eagleeyenetworks/eagleeyenetworks_test.go
deleted file mode 100644
index ce669e693d2f..000000000000
--- a/pkg/detectors/eagleeyenetworks/eagleeyenetworks_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package eagleeyenetworks
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Token"
- in: "Body"
- api_version: v1
- eagleeyenetworks_email: "test08@eagleeyenetworks.com"
- eagleeyenetworks_secret: "Y6YWq0NfYgyJCL0"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "Y6YWq0NfYgyJCL0"
-)
-
-func TestEagleEyeNetworks_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/easyinsight/easyinsight.go b/pkg/detectors/easyinsight/easyinsight.go
deleted file mode 100644
index c51387bd881d..000000000000
--- a/pkg/detectors/easyinsight/easyinsight.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package easyinsight
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"easyinsight", "easy-insight", "key"}) + `\b([0-9a-zA-Z]{20})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"easyinsight", "easy-insight", "id"}) + `\b([a-zA-Z0-9]{20})\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{"easyinsight", "easy-insight"}
-}
-
-// FromData will find and optionally verify EasyInsight secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var keyMatches, idMatches = make(map[string]struct{}), make(map[string]struct{})
-
- // get unique key and id matches
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatches[matches[1]] = struct{}{}
- }
-
- for _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {
- idMatches[matches[1]] = struct{}{}
- }
-
- for keyMatch := range keyMatches {
- for idMatch := range idMatches {
- //as key and id regex are same, the strings captured by both regex will be same.
- //avoid processing when key is same as id. This will allow detector to process only different combinations
- if keyMatch == idMatch {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_EasyInsight,
- Raw: []byte(keyMatch),
- RawV2: []byte(keyMatch + idMatch),
- }
-
- if verify {
- verified, verificationErr := verifyEasyInsight(ctx, idMatch, keyMatch)
- s1.Verified = verified
- if verificationErr != nil {
- s1.SetVerificationError(verificationErr)
- }
- }
-
- results = append(results, s1)
- // if key id combination is verified, skip other idMatches for that key
- if s1.Verified {
- break
- }
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_EasyInsight
-}
-
-func (s Scanner) Description() string {
- return "EasyInsight is a business intelligence tool that provides data visualization and reporting. EasyInsight API keys can be used to access and manage data within the platform."
-}
-
-func verifyEasyInsight(ctx context.Context, id, key string) (bool, error) {
- // docs: https://www.easy-insight.com/api/users.html
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.easy-insight.com/app/api/users.json", nil)
- if err != nil {
- return false, err
- }
-
- // add required headers to the request
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Accept", "application/json")
- // set basic auth for the request
- req.SetBasicAuth(id, key)
-
- res, reqErr := client.Do(req)
- if reqErr != nil {
- return false, reqErr
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- // id, key verified
- case http.StatusOK:
- return true, nil
- // id, key unverified
- case http.StatusUnauthorized:
- return false, nil
- // something invalid
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
diff --git a/pkg/detectors/easyinsight/easyinsight_integration_test.go b/pkg/detectors/easyinsight/easyinsight_integration_test.go
deleted file mode 100644
index 098b8a3a3954..000000000000
--- a/pkg/detectors/easyinsight/easyinsight_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package easyinsight
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEasyInsight_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("EASYINSIGHT")
- inactiveSecret := testSecrets.MustGetField("EASYINSIGHT_INACTIVE")
- id := testSecrets.MustGetField("EASYINSIGHT_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a easyinsight secret %s within easyid %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EasyInsight,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a easyinsight secret %s within easyid %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EasyInsight,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("EasyInsight.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("EasyInsight.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/easyinsight/easyinsight_test.go b/pkg/detectors/easyinsight/easyinsight_test.go
deleted file mode 100644
index 00ef59366d40..000000000000
--- a/pkg/detectors/easyinsight/easyinsight_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package easyinsight
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKeyPattern = "987ahjjdasgUcaaraAdd"
- validIDPattern = "poiuy76RaEf90ertgh0K"
- // this should result in 4 combinations
- complexPattern = `easyinsight credentials
- these credentials are for testing a pattern
- key: A876AcaraTsaAKcae09a
- id: chECk12345ChecK12345
- -------------------------
- second credentials:
- key: B874CDaraTsaAKVBe08A
- id: CHECK12345ChecK09876`
- invalidPattern = "poiuy76=a_$90ertgh0K"
-)
-
-func TestEasyInsight_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: fmt.Sprintf("easyinsight key = '%s' easy-insight id = '%s", validKeyPattern, validIDPattern),
- want: []string{validKeyPattern + validIDPattern, validIDPattern + validKeyPattern},
- },
- {
- name: "valid pattern - complex",
- input: fmt.Sprintf("easyinsight token = '%s'", complexPattern),
- want: []string{
- "A876AcaraTsaAKcae09achECk12345ChecK12345",
- "A876AcaraTsaAKcae09aCHECK12345ChecK09876",
- "B874CDaraTsaAKVBe08ACHECK12345ChecK09876",
- "B874CDaraTsaAKVBe08AchECk12345ChecK12345",
- },
- },
- {
- name: "valid pattern - out of prefix range",
- input: fmt.Sprintf("easyinsight key and id keyword is not close to the real token = '%s|%s'", validKeyPattern, validIDPattern),
- want: nil,
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("easyinsight = '%s|%s'", invalidPattern, invalidPattern),
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/ecostruxureit/ecostruxureit.go b/pkg/detectors/ecostruxureit/ecostruxureit.go
deleted file mode 100644
index de2b7fce8cc3..000000000000
--- a/pkg/detectors/ecostruxureit/ecostruxureit.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package ecostruxureit
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ecostruxureit"}) + `\b(AK1[0-9a-zA-Z\/]{50,55})\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{"ecostruxureit"}
-}
-
-// FromData will find and optionally verify EcoStruxureIT secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_EcoStruxureIT,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.ecostruxureit.com/rest/v1/organizations", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_EcoStruxureIT
-}
-
-func (s Scanner) Description() string {
- return "EcoStruxure IT is a cloud-based platform that provides IT infrastructure management. EcoStruxure IT API keys can be used to access and manage IT infrastructure data and operations."
-}
diff --git a/pkg/detectors/ecostruxureit/ecostruxureit_integration_test.go b/pkg/detectors/ecostruxureit/ecostruxureit_integration_test.go
deleted file mode 100644
index 151efcdba6e9..000000000000
--- a/pkg/detectors/ecostruxureit/ecostruxureit_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package ecostruxureit
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEcoStruxureIT_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ECOSTRUXUREIT")
- inactiveSecret := testSecrets.MustGetField("ECOSTRUXUREIT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ecostruxureit secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EcoStruxureIT,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ecostruxureit secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EcoStruxureIT,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("EcoStruxureIT.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("EcoStruxureIT.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/ecostruxureit/ecostruxureit_test.go b/pkg/detectors/ecostruxureit/ecostruxureit_test.go
deleted file mode 100644
index 6735dbc3a016..000000000000
--- a/pkg/detectors/ecostruxureit/ecostruxureit_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package ecostruxureit
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- api_version: v1
- ecostruxureit_secret: "AK1CI5QsL1zvRE5KBX/uL9KuiZOJq27GwcJu4fV/xyTJcYCrYP0ykE"
- base_url: "https://api.example.com/$api_version/example"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "AK1CI5QsL1zvRE5KBX/uL9KuiZOJq27GwcJu4fV/xyTJcYCrYP0ykE"
-)
-
-func TestEcostruxureit_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/edamam/edamam.go b/pkg/detectors/edamam/edamam.go
deleted file mode 100644
index 5a72549dcafd..000000000000
--- a/pkg/detectors/edamam/edamam.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package edamam
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"edamam"}) + `\b([0-9a-z]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"edamam"}) + `\b([0-9a-z]{8})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"edamam"}
-}
-
-// FromData will find and optionally verify Edamam secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, idMatch := range idMatches {
- resId := strings.TrimSpace(idMatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Edamam,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resId),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.edamam.com/auto-complete?app_id=%s&app_key=%s&q=%s", resId, resMatch, ""), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Edamam
-}
-
-func (s Scanner) Description() string {
- return "Edamam provides nutrition analysis and diet recommendations. Edamam API keys can be used to access and modify nutrition data and perform diet analysis."
-}
diff --git a/pkg/detectors/edamam/edamam_integration_test.go b/pkg/detectors/edamam/edamam_integration_test.go
deleted file mode 100644
index af09b26d7caa..000000000000
--- a/pkg/detectors/edamam/edamam_integration_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package edamam
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEdamam_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("EDAMAM")
- id := testSecrets.MustGetField("EDAMAM_ID")
- inactiveSecret := testSecrets.MustGetField("EDAMAM_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a edamam secret %s within edamam id %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Edamam,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a edamam secret %s within edamam id %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Edamam,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Edamam.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no raw v2 secret present: \n %+v", got[i])
- }
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Edamam.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/edamam/edamam_test.go b/pkg/detectors/edamam/edamam_test.go
deleted file mode 100644
index b5edfce947d6..000000000000
--- a/pkg/detectors/edamam/edamam_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package edamam
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- api_version: v1
- edamam_id: "1vsqsubh"
- edamam_secret: "e3at3vut4x27aq5wpkjmivjt9kq5cune"
- base_url: "https://api.example.com/$api_version/example"
- query: "app_id=$edamam_id&app_key=$edamam_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "e3at3vut4x27aq5wpkjmivjt9kq5cune1vsqsubh"
-)
-
-func TestEdamam_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/edenai/edenai.go b/pkg/detectors/edenai/edenai.go
deleted file mode 100644
index 6eeab40acd4c..000000000000
--- a/pkg/detectors/edenai/edenai.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package edenai
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"edenai"}) + `\b([a-zA-Z0-9]{36}.[a-zA-Z0-9]{92}.[a-zA-Z0-9_]{43})\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{"edenai"}
-}
-
-// FromData will find and optionally verify EdenAI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_EdenAI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.edenai.run/v1/automl/text/project", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_EdenAI
-}
-
-func (s Scanner) Description() string {
- return "EdenAI provides a unified API to access multiple AI engines. EdenAI API keys can be used to access and utilize these AI services."
-}
diff --git a/pkg/detectors/edenai/edenai_integration_test.go b/pkg/detectors/edenai/edenai_integration_test.go
deleted file mode 100644
index 6852f229908d..000000000000
--- a/pkg/detectors/edenai/edenai_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package edenai
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEdenAI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("EDENAI")
- inactiveSecret := testSecrets.MustGetField("EDENAI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a edenai secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EdenAI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a edenai secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EdenAI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("EdenAI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("EdenAI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/edenai/edenai_test.go b/pkg/detectors/edenai/edenai_test.go
deleted file mode 100644
index 9e0e97563218..000000000000
--- a/pkg/detectors/edenai/edenai_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package edenai
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- api_version: v1
- edenai_secret: "CQcxzQhT70xdDr8J6zGDpTY4Iv3ro10k1YMG}XLr0DyMXgnxMCqx4m92bgOK5QkBZJJNSoOHk8y6yEuoIu6MBb5I12Jbrjw9TpMWUf8dgxSlFyvFpyUOz5A3gvJu926a4F17oRzQpAfBAjGpL91ZxNtZ5uDy50MNnh1VgadWFnRzR"
- base_url: "https://api.example.com/$api_version/example"
- query: ""
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "CQcxzQhT70xdDr8J6zGDpTY4Iv3ro10k1YMG}XLr0DyMXgnxMCqx4m92bgOK5QkBZJJNSoOHk8y6yEuoIu6MBb5I12Jbrjw9TpMWUf8dgxSlFyvFpyUOz5A3gvJu926a4F17oRzQpAfBAjGpL91ZxNtZ5uDy50MNnh1VgadWFnRzR"
-)
-
-func TestEdenai_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/eightxeight/eightxeight.go b/pkg/detectors/eightxeight/eightxeight.go
deleted file mode 100644
index d2cb05b26518..000000000000
--- a/pkg/detectors/eightxeight/eightxeight.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package eightxeight
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"8x8"}) + `\b([a-zA-Z0-9]{43})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"8x8"}) + `\b([a-zA-Z0-9_]{18,30})\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{"8x8"}
-}
-
-// FromData will find and optionally verify EightxEight secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- resIdMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_EightxEight,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resIdMatch),
- }
-
- if verify {
- timeout := 10 * time.Second
- client.Timeout = timeout
- payload := strings.NewReader(`{"source":"abcde","destination":"+6512345678","text":"Hello World!","encoding":"AUTO"}`)
- req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("https://sms.8x8.com/api/v1/subaccounts/%s/messages", resIdMatch), payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_EightxEight
-}
-
-func (s Scanner) Description() string {
- return "8x8 is a provider of cloud-based communication services including voice, video, chat, and contact center solutions."
-}
diff --git a/pkg/detectors/eightxeight/eightxeight_integration_test.go b/pkg/detectors/eightxeight/eightxeight_integration_test.go
deleted file mode 100644
index 91ba987cfdc0..000000000000
--- a/pkg/detectors/eightxeight/eightxeight_integration_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package eightxeight
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEightxEight_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("EIGHTXEIGHT")
- id := testSecrets.MustGetField("EIGHTXEIGHT_ID")
- inactiveSecret := testSecrets.MustGetField("EIGHTXEIGHT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a 8x8 secret %s within 8x8 %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EightxEight,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a 8x8 secret %s within 8x8 %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EightxEight,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("EightxEight.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("EightxEight.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/eightxeight/eightxeight_test.go b/pkg/detectors/eightxeight/eightxeight_test.go
deleted file mode 100644
index 78fcdb2153b1..000000000000
--- a/pkg/detectors/eightxeight/eightxeight_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package eightxeight
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- api_version: v1
- 8x8_id: "ByvWSRLcNhS_bgLBjD4hAhUvkWLz"
- 8x8_secret: "LiE1BOtWbU7YucNYPnXNG0LIFlkfWcMt8KLBu1MfjeS"
- base_url: "https://api.example.com/$api_version/example"
- query: ""
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "LiE1BOtWbU7YucNYPnXNG0LIFlkfWcMt8KLBu1MfjeSByvWSRLcNhS_bgLBjD4hAhUvkWLz"
-)
-
-func TestEightXEight_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/elasticemail/elasticemail.go b/pkg/detectors/elasticemail/elasticemail.go
deleted file mode 100644
index a8f7d06f9ca1..000000000000
--- a/pkg/detectors/elasticemail/elasticemail.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package elasticemail
-
-import (
- "context"
- "encoding/json"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"elastic"}) + `\b([A-Za-z0-9_-]{96})\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{"elasticemail"}
-}
-
-// FromData will find and optionally verify ElasticEmail secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ElasticEmail,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.elasticemail.com/v2/account/profileoverview?apikey="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- data, readErr := io.ReadAll(res.Body)
- res.Body.Close()
- if readErr == nil {
- var ResVar struct {
- Success bool `json:"success"`
- }
- if err := json.Unmarshal(data, &ResVar); err == nil {
- if ResVar.Success {
- s1.Verified = true
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ElasticEmail
-}
-
-func (s Scanner) Description() string {
- return "ElasticEmail is an email marketing service. ElasticEmail API keys can be used to send emails, manage contacts, and access other features of the service."
-}
diff --git a/pkg/detectors/elasticemail/elasticemail_integration_test.go b/pkg/detectors/elasticemail/elasticemail_integration_test.go
deleted file mode 100644
index a33f76eb2a1f..000000000000
--- a/pkg/detectors/elasticemail/elasticemail_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package elasticemail
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestElasticEmail_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ELASTICEMAIL_TOKEN")
- inactiveSecret := testSecrets.MustGetField("ELASTICEMAIL_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a elasticemail secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ElasticEmail,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a elasticemail secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ElasticEmail,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ElasticEmail.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ElasticEmail.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/elasticemail/elasticemail_test.go b/pkg/detectors/elasticemail/elasticemail_test.go
deleted file mode 100644
index d3441cec8091..000000000000
--- a/pkg/detectors/elasticemail/elasticemail_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package elasticemail
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- api_version: v1
- elasticemail_secret: "KjzCaS0dOHBFkH6ljFkQp353jV8FH5Fgmo9-t9Bgl2iP1btjXEEaGwOPVnR8LZFSksLpL4kwxUXOFJBGwz6xBbVeJIR8K17p"
- base_url: "https://api.example.com/$api_version/example"
- query: "apiKey=$elasticemail_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "KjzCaS0dOHBFkH6ljFkQp353jV8FH5Fgmo9-t9Bgl2iP1btjXEEaGwOPVnR8LZFSksLpL4kwxUXOFJBGwz6xBbVeJIR8K17p"
-)
-
-func TestElasticEmail_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/elevenlabs/v1/elevenlabs.go b/pkg/detectors/elevenlabs/v1/elevenlabs.go
deleted file mode 100644
index 2edda0cdce8d..000000000000
--- a/pkg/detectors/elevenlabs/v1/elevenlabs.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package elevenlabs
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-func (Scanner) Version() int { return 1 }
-
-type UserRes struct {
- Subscription struct {
- Tier string `json:"tier"`
- } `json:"subscription"`
- Name string `json:"first_name"`
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`(?i)(?:elevenlabs|xi-api-key|el|token|key)[^\.].{0,40}[ =:'"]+([a-f0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"elevenlabs", "xi-api-key", "xi_api_key"}
-}
-
-// FromData will find and optionally verify Elevenlabs secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ElevenLabs,
- Raw: []byte(match),
- ExtraData: map[string]string{
- "version": "1",
- "rotation_guide": "https://howtorotate.com/docs/tutorials/elevenlabs/",
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, userResponse, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- if userResponse != nil {
- s1.ExtraData["Name"] = userResponse.Name
- s1.ExtraData["Tier"] = userResponse.Subscription.Tier
- }
- s1.SetVerificationError(verificationErr, match)
-
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{
- "key": match,
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, *UserRes, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.elevenlabs.io/v1/user", nil)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Set("xi-api-key", token)
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- // If the endpoint returns useful information, we can return it as a map.
- var userResponse UserRes
- if err = json.NewDecoder(res.Body).Decode(&userResponse); err != nil {
- return false, nil, err
- }
- return true, &userResponse, nil
- case http.StatusBadRequest, http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ElevenLabs
-}
-
-func (s Scanner) Description() string {
- return "Elevenlabs is an AI-driven voice synthesis platform. Elevenlabs API keys can be used to access and manipulate voice synthesis features and services."
-}
diff --git a/pkg/detectors/elevenlabs/v1/elevenlabs_integration_test.go b/pkg/detectors/elevenlabs/v1/elevenlabs_integration_test.go
deleted file mode 100644
index 408eb0275701..000000000000
--- a/pkg/detectors/elevenlabs/v1/elevenlabs_integration_test.go
+++ /dev/null
@@ -1,27 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package elevenlabs
-
-import (
- "context"
- "testing"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-)
-
-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)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/elevenlabs/v1/elevenlabs_test.go b/pkg/detectors/elevenlabs/v1/elevenlabs_test.go
deleted file mode 100644
index c24a3af5ee71..000000000000
--- a/pkg/detectors/elevenlabs/v1/elevenlabs_test.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package elevenlabs
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestElevenlabs_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: "XI_API_KEY = 'b41b9d78aefb8c7c6cf9ebf01231340b'",
- want: []string{"b41b9d78aefb8c7c6cf9ebf01231340b"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/elevenlabs/v2/elevenlabs.go b/pkg/detectors/elevenlabs/v2/elevenlabs.go
deleted file mode 100644
index 1e094d12e91a..000000000000
--- a/pkg/detectors/elevenlabs/v2/elevenlabs.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package elevenlabs
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-func (Scanner) Version() int { return 2 }
-
-type UserRes struct {
- Subscription struct {
- Tier string `json:"tier"`
- } `json:"subscription"`
- Name string `json:"first_name"`
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b((?:sk)_[a-f0-9]{48})\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{"elevenlabs", "xi-api-key", "xi_api_key"}
-}
-
-// FromData will find and optionally verify Elevenlabs secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ElevenLabs,
- Raw: []byte(match),
- ExtraData: map[string]string{"version": "2"},
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, userResponse, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- if userResponse != nil {
- s1.ExtraData["Name"] = userResponse.Name
- s1.ExtraData["Tier"] = userResponse.Subscription.Tier
- }
- s1.SetVerificationError(verificationErr, match)
-
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{
- "key": match,
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func (s Scanner) Description() string {
- return "ElevenLabs is a service that provides API keys for accessing their voice synthesis and other AI-powered tools. These keys can be used to interact with ElevenLabs' services programmatically."
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, *UserRes, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.elevenlabs.io/v1/user", nil)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Set("xi-api-key", token)
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- // If the endpoint returns useful information, we can return it as a map.
- var userResponse UserRes
- if err = json.NewDecoder(res.Body).Decode(&userResponse); err != nil {
- return false, nil, err
- }
- return true, &userResponse, nil
- case http.StatusBadRequest, http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ElevenLabs
-}
diff --git a/pkg/detectors/elevenlabs/v2/elevenlabs_integration_test.go b/pkg/detectors/elevenlabs/v2/elevenlabs_integration_test.go
deleted file mode 100644
index 759c58c439e4..000000000000
--- a/pkg/detectors/elevenlabs/v2/elevenlabs_integration_test.go
+++ /dev/null
@@ -1,175 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package elevenlabs
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestElevenlabs_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ELEVENLABS")
- inactiveSecret := testSecrets.MustGetField("ELEVENLABS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a elevenlabs secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ElevenLabs,
- Verified: true,
- ExtraData: map[string]string{
- "version": "2",
- "Name": "Trufflesecurity",
- "Tier": "free",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a elevenlabs secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ElevenLabs,
- Verified: false,
- ExtraData: map[string]string{
- "version": "2",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a elevenlabs secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ElevenLabs,
- Verified: false,
- ExtraData: map[string]string{
- "version": "2",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a elevenlabs secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ElevenLabs,
- Verified: false,
- ExtraData: map[string]string{
- "version": "2",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Elevenlabs.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Elevenlabs.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/elevenlabs/v2/elevenlabs_test.go b/pkg/detectors/elevenlabs/v2/elevenlabs_test.go
deleted file mode 100644
index 1523459f78e6..000000000000
--- a/pkg/detectors/elevenlabs/v2/elevenlabs_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package elevenlabs
-
-import (
- "context"
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
- "testing"
-)
-
-func TestElevenlabs_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: "XI_API_KEY = 'sk_c43667f9bedd46fcff858f09f648d984533645e30f0541df'",
- want: []string{"sk_c43667f9bedd46fcff858f09f648d984533645e30f0541df"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/enablex/enablex.go b/pkg/detectors/enablex/enablex.go
deleted file mode 100644
index 75282065763b..000000000000
--- a/pkg/detectors/enablex/enablex.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package enablex
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"enablex"}) + `\b([a-zA-Z0-9]{36})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"enablex"}) + `\b([a-z0-9]{24})\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{"enablex"}
-}
-
-// FromData will find and optionally verify Enablex secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- tokenPatMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- userPatMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Enablex,
- Raw: []byte(tokenPatMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.enablex.io/voice/v1/call", nil)
- if err != nil {
- continue
- }
- req.SetBasicAuth(userPatMatch, tokenPatMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Enablex
-}
-
-func (s Scanner) Description() string {
- return "Enablex is a communication platform offering voice, video, and messaging APIs. Enablex credentials can be used to access and manage communication services provided by Enablex."
-}
diff --git a/pkg/detectors/enablex/enablex_integration_test.go b/pkg/detectors/enablex/enablex_integration_test.go
deleted file mode 100644
index fc098a806a54..000000000000
--- a/pkg/detectors/enablex/enablex_integration_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package enablex
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEnablex_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ENABLEX")
- user := testSecrets.MustGetField("ENABLEX_USER")
- inactiveSecret := testSecrets.MustGetField("ENABLEX_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a enablex secret %s within enablex %s", secret, user)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Enablex,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a enablex secret %s within enablex %s but not valid", inactiveSecret, user)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Enablex,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Enablex.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Enablex.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/enablex/enablex_test.go b/pkg/detectors/enablex/enablex_test.go
deleted file mode 100644
index b372a9169dfe..000000000000
--- a/pkg/detectors/enablex/enablex_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package enablex
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Basic"
- in: "Header"
- api_version: v1
- enablex_id: "hkhihhsneir2aablmbk55u8f"
- enablex_secret: "iSgJYVk9ZhWwgLTH9hyTv1IjqIKUNeX6B623"
- base_url: "https://api.example.com/$api_version/example"
- query: ""
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "iSgJYVk9ZhWwgLTH9hyTv1IjqIKUNeX6B623"
-)
-
-func TestEnableX_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/endorlabs/endorlabs.go b/pkg/detectors/endorlabs/endorlabs.go
deleted file mode 100644
index 4aae116d14d6..000000000000
--- a/pkg/detectors/endorlabs/endorlabs.go
+++ /dev/null
@@ -1,118 +0,0 @@
-package endorlabs
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyAndSecretPat = regexp.MustCompile(`\b(endr\+[a-zA-Z0-9-]{16})\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{"endr+"}
-}
-
-// FromData will find and optionally verify Endorlabs secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keyMatches := make(map[string]struct{})
- for _, match := range keyAndSecretPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatches[match[1]] = struct{}{}
- }
-
- secretMatches := make(map[string]struct{})
- for _, match := range keyAndSecretPat.FindAllStringSubmatch(dataStr, -1) {
- secretMatches[match[1]] = struct{}{}
- }
-
- for key := range keyMatches {
- for secret := range secretMatches {
- if key == secret { // Minor optimization
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_EndorLabs,
- Raw: []byte(key),
- RawV2: []byte(key + secret),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, key, secret)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, key, secret)
- }
-
- results = append(results, s1)
- }
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, key, secret string) (bool, map[string]string, error) {
- authData := fmt.Sprintf(`{"key":"%s", "secret":"%s"}`, key, secret)
-
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.endorlabs.com/v1/auth/api-key", strings.NewReader(authData))
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Add("Content-Type", "application/json")
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- // If the endpoint returns useful information, we can return it as a map.
- return true, nil, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_EndorLabs
-}
-
-func (s Scanner) Description() string {
- return "Endorlabs provides API keys that can be used to authenticate and interact with its services. These keys should be kept confidential to prevent unauthorized access."
-}
diff --git a/pkg/detectors/endorlabs/endorlabs_integration_test.go b/pkg/detectors/endorlabs/endorlabs_integration_test.go
deleted file mode 100644
index 313c0b291074..000000000000
--- a/pkg/detectors/endorlabs/endorlabs_integration_test.go
+++ /dev/null
@@ -1,182 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package endorlabs
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEndorlabs_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- key := testSecrets.MustGetField("ENDOR_KEY")
- secret := testSecrets.MustGetField("ENDOR_SECRET")
- inactiveKey := testSecrets.MustGetField("ENDOR_KEY_INACTIVE")
- inactiveSecret := testSecrets.MustGetField("ENDOR_SECRET_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a endorlabs key %s and endorlabs secret %s within", key, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EndorLabs,
- Verified: true,
- },
- {
- DetectorType: detectorspb.DetectorType_EndorLabs,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a endorlabs key %s and endorlabs secret %s within but not valid", inactiveKey, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EndorLabs,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_EndorLabs,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a endorlabs key %s and endorlabs secret %s within", key, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EndorLabs,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_EndorLabs,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a endorlabs key %s and endorlabs secret %s within", key, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EndorLabs,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_EndorLabs,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Endorlabs.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
- sortOpts := cmpopts.SortSlices(func(a, b []detectors.Result) bool {
- return string(a[0].Raw) < string(b[0].Raw)
- })
- if diff := cmp.Diff(got, tt.want, ignoreOpts, sortOpts); diff != "" {
- t.Errorf("Endorlabs.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/endorlabs/endorlabs_test.go b/pkg/detectors/endorlabs/endorlabs_test.go
deleted file mode 100644
index 6256d8dc3bf9..000000000000
--- a/pkg/detectors/endorlabs/endorlabs_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package endorlabs
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestEndorlabs_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: `
- endorlabs_key = 'endr+xTGNjttLb8kVOHZC'
- endorlabs_secret = 'endr+gGVYIIrCq1VZTQMW'
- `,
- want: []string{
- "endr+xTGNjttLb8kVOHZC" + "endr+gGVYIIrCq1VZTQMW",
- "endr+gGVYIIrCq1VZTQMW" + "endr+xTGNjttLb8kVOHZC",
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/endpoint_customizer.go b/pkg/detectors/endpoint_customizer.go
deleted file mode 100644
index 7cc917d8528f..000000000000
--- a/pkg/detectors/endpoint_customizer.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package detectors
-
-import (
- "fmt"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
-)
-
-// EndpointSetter implements a sensible default for the SetEndpoints function
-// of the EndpointCustomizer interface. A detector can embed this struct to
-// gain the functionality.
-type EndpointSetter struct {
- configuredEndpoints []string
- cloudEndpoint string
- useCloudEndpoint bool
- useFoundEndpoints bool
-}
-
-func (e *EndpointSetter) SetConfiguredEndpoints(userConfiguredEndpoints ...string) error {
- if len(userConfiguredEndpoints) == 0 {
- return fmt.Errorf("at least one endpoint required")
- }
- deduped := make([]string, 0, len(userConfiguredEndpoints))
- for _, endpoint := range userConfiguredEndpoints {
- common.AddStringSliceItem(endpoint, &deduped)
- }
- e.configuredEndpoints = deduped
- return nil
-}
-
-func (e *EndpointSetter) SetCloudEndpoint(url string) {
- e.cloudEndpoint = url
-}
-
-func (e *EndpointSetter) UseCloudEndpoint(enabled bool) {
- e.useCloudEndpoint = enabled
-}
-
-func (e *EndpointSetter) UseFoundEndpoints(enabled bool) {
- e.useFoundEndpoints = enabled
-}
-
-func (e *EndpointSetter) Endpoints(foundEndpoints ...string) []string {
- endpoints := e.configuredEndpoints
- if e.useCloudEndpoint && e.cloudEndpoint != "" {
- endpoints = append(endpoints, e.cloudEndpoint)
- }
- if e.useFoundEndpoints {
- endpoints = append(endpoints, foundEndpoints...)
- }
- return endpoints
-}
diff --git a/pkg/detectors/endpoint_customizer_test.go b/pkg/detectors/endpoint_customizer_test.go
deleted file mode 100644
index 0f54118b5eb1..000000000000
--- a/pkg/detectors/endpoint_customizer_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package detectors
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestEmbeddedEndpointSetter(t *testing.T) {
- type Scanner struct{ EndpointSetter }
-
- var s Scanner
-
- t.Run("useFoundEndpoints is true", func(t *testing.T) {
- s.useFoundEndpoints = true
-
- // "baz" is passed to Endpoints, should appear in the result
- assert.Equal(t, []string{"baz"}, s.Endpoints("baz"))
- })
-
- t.Run("setting configured endpoints", func(t *testing.T) {
- // Setting "foo" and "bar"
- assert.NoError(t, s.SetConfiguredEndpoints("foo", "bar"))
-
- // Returning error because no endpoints are passed
- assert.Error(t, s.SetConfiguredEndpoints())
- })
-
- // "foo" and "bar" are added as configured endpoint
-
- t.Run("useFoundEndpoints adds new endpoints", func(t *testing.T) {
- // "baz" is added because useFoundEndpoints is true
- assert.Equal(t, []string{"foo", "bar", "baz"}, s.Endpoints("baz"))
- })
-
- t.Run("useCloudEndpoint is true", func(t *testing.T) {
- s.useCloudEndpoint = true
- s.cloudEndpoint = "test"
-
- // "test" is added because useCloudEndpoint is true and cloudEndpoint is set
- assert.Equal(t, []string{"foo", "bar", "test"}, s.Endpoints())
- })
-
- t.Run("disable both foundEndpoints and cloudEndpoint", func(t *testing.T) {
- // now disable both useFoundEndpoints and useCloudEndpoint
- s.useFoundEndpoints = false
- s.useCloudEndpoint = false
-
- // "test" won't be added
- assert.Equal(t, []string{"foo", "bar"}, s.Endpoints("test"))
- })
-
- t.Run("cloudEndpoint not added when useCloudEndpoint is false", func(t *testing.T) {
- s.cloudEndpoint = "new"
-
- // "new" is not added because useCloudEndpoint is false
- assert.Equal(t, []string{"foo", "bar"}, s.Endpoints())
- })
-
-}
diff --git a/pkg/detectors/enigma/enigma.go b/pkg/detectors/enigma/enigma.go
deleted file mode 100644
index 3ce901485428..000000000000
--- a/pkg/detectors/enigma/enigma.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package enigma
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"enigma"}) + `\b([a-zA-Z0-9]{40})\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{"enigma"}
-}
-
-// FromData will find and optionally verify Enigma secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Enigma,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{"name":"Enigma Technologies, Inc.","person":{"first_name":"","last_name":""},"address":{"street_address1":"245 5th Ave","street_address2":"","city":"New York","state":"NY","postal_code":"10016"}}`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.enigma.com/businesses/match", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("x-api-key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Enigma
-}
-
-func (s Scanner) Description() string {
- return "Enigma is a data intelligence company that provides comprehensive data about businesses. Enigma API keys can be used to access and interact with this data."
-}
diff --git a/pkg/detectors/enigma/enigma_integration_test.go b/pkg/detectors/enigma/enigma_integration_test.go
deleted file mode 100644
index 78065e14ec49..000000000000
--- a/pkg/detectors/enigma/enigma_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package enigma
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEnigma_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ENIGMA")
- inactiveSecret := testSecrets.MustGetField("ENIGMA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a enigma secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Enigma,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a enigma secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Enigma,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Enigma.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Enigma.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/enigma/enigma_test.go b/pkg/detectors/enigma/enigma_test.go
deleted file mode 100644
index 5d6b32e583a2..000000000000
--- a/pkg/detectors/enigma/enigma_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package enigma
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- api_version: v1
- enigma_secret: "dkQePsD59DdzfoSuIZ2Po2md3q0ENVnvyIDdxs2E"
- base_url: "https://api.example.com/$api_version/example"
- query: ""
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "dkQePsD59DdzfoSuIZ2Po2md3q0ENVnvyIDdxs2E"
-)
-
-func TestEnigma_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/envoyapikey/envoyapikey.go b/pkg/detectors/envoyapikey/envoyapikey.go
deleted file mode 100644
index 3428ba0b3018..000000000000
--- a/pkg/detectors/envoyapikey/envoyapikey.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package envoyapikey
-
-import (
- "context"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"envoy"}) + `\b([a-zA-Z0-9]{220})\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{"envoy"}
-}
-
-// FromData will find and optionally verify Envoy secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_EnvoyApiKey,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.envoy.com/v1/locations", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.envoy+json; version=3")
- req.Header.Add("X-Api-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- body, _ := io.ReadAll(res.Body)
-
- // Invalid API keys can also return status code 200, so check for presence of 'status 401' in response body.
- if res.StatusCode >= 200 && res.StatusCode < 300 || res.StatusCode == 403 {
- if !strings.Contains(string(body), `"status":401`) {
- s1.Verified = true
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_EnvoyApiKey
-}
-
-func (s Scanner) Description() string {
- return "Envoy is a cloud-based platform that provides visitor management solutions. Envoy API keys can be used to access and manage visitor data and other resources within the Envoy platform."
-}
diff --git a/pkg/detectors/envoyapikey/envoyapikey_integration_test.go b/pkg/detectors/envoyapikey/envoyapikey_integration_test.go
deleted file mode 100644
index 00b3536146da..000000000000
--- a/pkg/detectors/envoyapikey/envoyapikey_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package envoyapikey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEnvoyapikey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ENVOYAPIKEY")
- inactiveSecret := testSecrets.MustGetField("ENVOYAPIKEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a envoyapikey secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EnvoyApiKey,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a envoyapikey secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_EnvoyApiKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Envoyapikey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Envoyapikey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/envoyapikey/envoyapikey_test.go b/pkg/detectors/envoyapikey/envoyapikey_test.go
deleted file mode 100644
index 5d6e6fca35f3..000000000000
--- a/pkg/detectors/envoyapikey/envoyapikey_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package envoyapikey
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- api_version: v1
- envoy_secret: "53PbWnxV5h7pZGNmw7U6FL79ithvedz1PWSvhFyJDZbqT5ECihUDeQ4MY6O3qTtKMKNFh2Hc5D54pchSKYyTVKi3nqJITLhZi17uCHJVQKrinOrkGL9IUh6QFjDjN3NcK1HKAimUgcNY2B8meGBfQmQ2QnVhKZcK1E8ldT9w4eb9ihgEwnG2lMjG41k5bZEPos3sJDEJWZ39U2J2Yu6OP8h8AVLw"
- base_url: "https://api.example.com/$api_version/example"
- query: ""
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "53PbWnxV5h7pZGNmw7U6FL79ithvedz1PWSvhFyJDZbqT5ECihUDeQ4MY6O3qTtKMKNFh2Hc5D54pchSKYyTVKi3nqJITLhZi17uCHJVQKrinOrkGL9IUh6QFjDjN3NcK1HKAimUgcNY2B8meGBfQmQ2QnVhKZcK1E8ldT9w4eb9ihgEwnG2lMjG41k5bZEPos3sJDEJWZ39U2J2Yu6OP8h8AVLw"
-)
-
-func TestEnvoyAPIKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/eraser/eraser.go b/pkg/detectors/eraser/eraser.go
deleted file mode 100644
index 21694ede18ad..000000000000
--- a/pkg/detectors/eraser/eraser.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package eraser
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"eraser"}) + `\b([0-9a-zA-Z]{20})\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{"eraser"}
-}
-
-// FromData will find and optionally verify Eraser secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Eraser,
- Raw: []byte(match),
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/eraser/",
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
- // https://docs.eraser.io/reference/generate-diagram-from-eraser-dsl
- payload := strings.NewReader("{\"elements\":[{\"type\":\"diagram\"}]}")
-
- url := "https://app.eraser.io/api/render/elements"
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, payload)
- if err != nil {
- return false, nil, err
- }
-
- req.Header = http.Header{"Authorization": []string{"Bearer " + token}}
- req.Header.Add("content-type", "application/json")
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil, nil
- case http.StatusUnauthorized:
- // 401 API token unauthorized
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- // 400 The request is missing the 'text' parameter
- // 500 Eraser was unable to generate a result
- // 503 Service temporarily unavailable. This may be the result of too many requests.
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Eraser
-}
-
-func (s Scanner) Description() string {
- return "Eraser is a tool used for generating diagrams from DSL. Eraser API tokens can be used to authenticate and interact with the Eraser API."
-}
diff --git a/pkg/detectors/eraser/eraser_integration_test.go b/pkg/detectors/eraser/eraser_integration_test.go
deleted file mode 100644
index 6827f6e955ed..000000000000
--- a/pkg/detectors/eraser/eraser_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package eraser
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEraser_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ERASER")
- inactiveSecret := testSecrets.MustGetField("ERASER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a eraser secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Eraser,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a eraser secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Eraser,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a eraser secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Eraser,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a eraser secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Eraser,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Eraser.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Eraser.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/eraser/eraser_test.go b/pkg/detectors/eraser/eraser_test.go
deleted file mode 100644
index 637baf5c0d01..000000000000
--- a/pkg/detectors/eraser/eraser_test.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package eraser
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestEraser_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: "eraser_token = 'KkBmh6TUBIcyFAp20XXa'",
- want: []string{"KkBmh6TUBIcyFAp20XXa"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/etherscan/etherscan.go b/pkg/detectors/etherscan/etherscan.go
deleted file mode 100644
index b220dc74c7c7..000000000000
--- a/pkg/detectors/etherscan/etherscan.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package etherscan
-
-import (
- "context"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"etherscan"}) + `\b([0-9A-Z]{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{"etherscan"}
-}
-
-// FromData will find and optionally verify Etherscan secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Etherscan,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.etherscan.io/api?module=account&action=balance&address=0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae&tag=latest&apikey="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
-
- if strings.Contains(body, `"OK"`) {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Etherscan
-}
-
-func (s Scanner) Description() string {
- return "Etherscan is a Block Explorer and Analytics Platform for Ethereum, a decentralized smart contracts platform. Etherscan API keys can be used to access various functionalities provided by Etherscan."
-}
diff --git a/pkg/detectors/etherscan/etherscan_integration_test.go b/pkg/detectors/etherscan/etherscan_integration_test.go
deleted file mode 100644
index 50bdfa29c2ea..000000000000
--- a/pkg/detectors/etherscan/etherscan_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package etherscan
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEtherscan_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ETHERSCAN")
- inactiveSecret := testSecrets.MustGetField("ETHERSCAN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a etherscan secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Etherscan,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a etherscan secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Etherscan,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Etherscan.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Etherscan.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/etherscan/etherscan_test.go b/pkg/detectors/etherscan/etherscan_test.go
deleted file mode 100644
index 433e0dcf9f8b..000000000000
--- a/pkg/detectors/etherscan/etherscan_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package etherscan
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- api_version: v1
- etherscan_secret: "9VROD0TR8VNW4ZEC0U2YK5W9X0B2HO1KAD"
- base_url: "https://api.example.com/$api_version/example"
- query: "apikey=$etherscan_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "9VROD0TR8VNW4ZEC0U2YK5W9X0B2HO1KAD"
-)
-
-func TestEtherScan_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/ethplorer/ethplorer.go b/pkg/detectors/ethplorer/ethplorer.go
deleted file mode 100644
index 4e3e9713cd71..000000000000
--- a/pkg/detectors/ethplorer/ethplorer.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package ethplorer
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ethplorer"}) + `\b([a-z0-9A-Z-]{22})\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{"ethplorer"}
-}
-
-// FromData will find and optionally verify Ethplorer secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Ethplorer,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader("apiKey=" + resMatch + "&addresses=0xb2930b35844a230f00e51431acae96fe543a0347%2C0xb52d3141ee731fac89927476c6a5207b37cd72ff")
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api-mon.ethplorer.io/createPool", payload)
- if err != nil {
- continue
- }
- req.Header.Add("accept", "application/json")
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Ethplorer
-}
-
-func (s Scanner) Description() string {
- return "Ethplorer API keys can be used to interact with the Ethplorer service, which provides access to Ethereum blockchain data and analytics."
-}
diff --git a/pkg/detectors/ethplorer/ethplorer_integration_test.go b/pkg/detectors/ethplorer/ethplorer_integration_test.go
deleted file mode 100644
index 1a010cb43352..000000000000
--- a/pkg/detectors/ethplorer/ethplorer_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package ethplorer
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEthplorer_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ETHPLORER")
- inactiveSecret := testSecrets.MustGetField("ETHPLORER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ethplorer secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Ethplorer,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ethplorer secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Ethplorer,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Ethplorer.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Ethplorer.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/ethplorer/ethplorer_test.go b/pkg/detectors/ethplorer/ethplorer_test.go
deleted file mode 100644
index c73828138880..000000000000
--- a/pkg/detectors/ethplorer/ethplorer_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package ethplorer
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Payload"
- api_version: v1
- ethplorer_secret: "QGp6JMwswjqb5FJFGuslKQ"
- base_url: "https://api.example.com/$api_version/example"
- query: ""
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "QGp6JMwswjqb5FJFGuslKQ"
-)
-
-func TestEthplorer_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/eventbrite/eventbrite.go b/pkg/detectors/eventbrite/eventbrite.go
deleted file mode 100644
index 56ebb1a13b90..000000000000
--- a/pkg/detectors/eventbrite/eventbrite.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package eventbrite
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"eventbrite"}) + `\b([0-9A-Z]{20})\b`)
-)
-
-func (s *Scanner) getClient() *http.Client {
- if s.client == nil {
- return defaultClient
- }
-
- return s.client
-}
-
-// 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{"eventbrite"}
-}
-
-// FromData will find and optionally verify Eventbrite secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueTokenMatches := make(map[string]struct{})
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokenMatches[match[1]] = struct{}{}
- }
-
- for token := range uniqueTokenMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Eventbrite,
- Raw: []byte(token),
- ExtraData: map[string]string{},
- }
-
- if verify {
- extraData, isVerified, verificationErr := verifyEventBrite(ctx, s.getClient(), token)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- s1.ExtraData = extraData
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Eventbrite
-}
-
-func (s Scanner) Description() string {
- return "Eventbrite is an event management and ticketing website. Eventbrite API keys can be used to access and manage event data."
-}
-
-func verifyEventBrite(ctx context.Context, client *http.Client, token string) (map[string]string, bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.eventbriteapi.com/v3/users/me/?token="+token, nil)
- if err != nil {
- return nil, false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- var response map[string]interface{}
- if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
- return nil, false, err
- }
-
- userName := response["name"].(string)
-
- return map[string]string{"user name": userName}, true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil, false, nil
- default:
- return nil, false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/eventbrite/eventbrite_integration_test.go b/pkg/detectors/eventbrite/eventbrite_integration_test.go
deleted file mode 100644
index eede39e28d59..000000000000
--- a/pkg/detectors/eventbrite/eventbrite_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package eventbrite
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEventbrite_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("EVENTBRITE")
- inactiveSecret := testSecrets.MustGetField("EVENTBRITE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a eventbrite secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Eventbrite,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a eventbrite secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Eventbrite,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a eventbrite secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Eventbrite,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a eventbrite secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Eventbrite,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Eventbrite.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Eventbrite.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/eventbrite/eventbrite_test.go b/pkg/detectors/eventbrite/eventbrite_test.go
deleted file mode 100644
index 64aeaf2e7b3a..000000000000
--- a/pkg/detectors/eventbrite/eventbrite_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package eventbrite
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- api_version: v1
- eventbrite_secret: "1SS1TOXV0S90JCAQ3G8F"
- base_url: "https://api.example.com/$api_version/example"
- query: "token=$eventbrite_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "1SS1TOXV0S90JCAQ3G8F"
-)
-
-func TestEventBrite_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/everhour/everhour.go b/pkg/detectors/everhour/everhour.go
deleted file mode 100644
index 1c006493ce8d..000000000000
--- a/pkg/detectors/everhour/everhour.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package everhour
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"everhour"}) + `\b([0-9Aa-f]{4}-[0-9a-f]{4}-[0-9a-f]{6}-[0-9a-f]{6}-[0-9a-f]{8})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"everhour"}
-}
-
-// FromData will find and optionally verify Everhour secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Everhour,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.everhour.com/clients", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-Api-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Everhour
-}
-
-func (s Scanner) Description() string {
- return "Everhour is a time tracking software for teams. Everhour API keys can be used to access and manage project and time tracking data."
-}
diff --git a/pkg/detectors/everhour/everhour_integration_test.go b/pkg/detectors/everhour/everhour_integration_test.go
deleted file mode 100644
index a2555b2623e9..000000000000
--- a/pkg/detectors/everhour/everhour_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package everhour
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestEverhour_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("EVERHOUR")
- inactiveSecret := testSecrets.MustGetField("EVERHOUR_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a everhour secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Everhour,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a everhour secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Everhour,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Everhour.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Everhour.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/everhour/everhour_test.go b/pkg/detectors/everhour/everhour_test.go
deleted file mode 100644
index 1e8cc3498eb6..000000000000
--- a/pkg/detectors/everhour/everhour_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package everhour
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- api_version: v1
- everhour_secret: "a289-1dad-dbeeeb-2c0b1f-dc0ed546"
- base_url: "https://api.example.com/$api_version/example"
- query: ""
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "a289-1dad-dbeeeb-2c0b1f-dc0ed546"
-)
-
-func TestEventBrite_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/exchangerateapi/exchangerateapi.go b/pkg/detectors/exchangerateapi/exchangerateapi.go
deleted file mode 100644
index 2932392227f9..000000000000
--- a/pkg/detectors/exchangerateapi/exchangerateapi.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package exchangerateapi
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"exchangerate", "exchange-rate"}) + `\b([a-f0-9]{24})\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{"exchangerate", "exchange-rate"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ExchangeRateAPI
-}
-
-func (s Scanner) Description() string {
- return "An API key for determining the exchange rate of currencies"
-}
-
-// FromData will find and optionally verify ExchangeRateAPI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ExchangeRateAPI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyExchangeRateKey(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyExchangeRateKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://v6.exchangerate-api.com/v6/latest/USD", http.NoBody)
- if err != nil {
- return false, err
- }
-
- // authentication docs: https://www.exchangerate-api.com/docs/authentication
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
- resp, err := client.Do(req)
- if err != nil {
- return false, nil
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/exchangerateapi/exchangerateapi_integration_test.go b/pkg/detectors/exchangerateapi/exchangerateapi_integration_test.go
deleted file mode 100644
index 89fa657ab99a..000000000000
--- a/pkg/detectors/exchangerateapi/exchangerateapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package exchangerateapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestExchangeRateAPI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("EXCHANGERATEAPI")
- inactiveSecret := testSecrets.MustGetField("EXCHANGERATEAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a exchangerateapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ExchangeRateAPI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a exchangerateapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ExchangeRateAPI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ExchangeRateAPI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ExchangeRateAPI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/exchangerateapi/exchangerateapi_test.go b/pkg/detectors/exchangerateapi/exchangerateapi_test.go
deleted file mode 100644
index 6045641983a4..000000000000
--- a/pkg/detectors/exchangerateapi/exchangerateapi_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package exchangerateapi
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestExchangeRateAPI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "Bearer"
- in: "Header"
- api_version: v1
- exchangerate_secret: "a1039cd66170a7bf214199d4"
- base_url: "https://api.example.com/$api_version/example"
- query: ""
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `,
- want: []string{"a1039cd66170a7bf214199d4"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/exchangeratesapi/exchangeratesapi.go b/pkg/detectors/exchangeratesapi/exchangeratesapi.go
deleted file mode 100644
index 12c6934ddef2..000000000000
--- a/pkg/detectors/exchangeratesapi/exchangeratesapi.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package exchangeratesapi
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"exchangerates"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"exchangerates"}
-}
-
-// FromData will find and optionally verify ExchangeRatesAPI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ExchangeRatesAPI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.exchangeratesapi.io/v1/latest?access_key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ExchangeRatesAPI
-}
-
-func (s Scanner) Description() string {
- return "ExchangeRatesAPI provides exchange rate data for various currencies. The API key can be used to access and retrieve this data."
-}
diff --git a/pkg/detectors/exchangeratesapi/exchangeratesapi_integration_test.go b/pkg/detectors/exchangeratesapi/exchangeratesapi_integration_test.go
deleted file mode 100644
index c3c7d216a671..000000000000
--- a/pkg/detectors/exchangeratesapi/exchangeratesapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package exchangeratesapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestExchangeRatesAPI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("EXCHANGERATESAPI")
- inactiveSecret := testSecrets.MustGetField("EXCHANGERATESAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a exchangeratesapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ExchangeRatesAPI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a exchangeratesapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ExchangeRatesAPI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ExchangeRatesAPI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ExchangeRatesAPI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/exchangeratesapi/exchangeratesapi_test.go b/pkg/detectors/exchangeratesapi/exchangeratesapi_test.go
deleted file mode 100644
index 0085562c0935..000000000000
--- a/pkg/detectors/exchangeratesapi/exchangeratesapi_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package exchangeratesapi
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- api_version: v1
- exchangerates_secret: "flo7en8mnclsnz50dme89e9vwr3l9jbb"
- base_url: "https://api.example.com/$api_version/example"
- query: "accesskey=$exchangerates_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "flo7en8mnclsnz50dme89e9vwr3l9jbb"
-)
-
-func TestExchangeRatesAPI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/exportsdk/exportsdk.go b/pkg/detectors/exportsdk/exportsdk.go
deleted file mode 100644
index b2761900361b..000000000000
--- a/pkg/detectors/exportsdk/exportsdk.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package exportsdk
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"exportsdk"}) + `\b([0-9a-z]{5,15}_[0-9a-z-]{36})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"exportsdk"}) + `\b([0-9a-z-]{36})\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{"exportsdk"}
-}
-
-// FromData will find and optionally verify ExportSDK secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ExportSDK,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{ "templateId": "` + resIdMatch + `"}`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.exportsdk.com/v1/pdf", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-API-KEY", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ExportSDK
-}
-
-func (s Scanner) Description() string {
- return "ExportSDK is a service used for exporting data and generating PDFs. ExportSDK keys can be used to authenticate API requests and generate documents."
-}
diff --git a/pkg/detectors/exportsdk/exportsdk_integration_test.go b/pkg/detectors/exportsdk/exportsdk_integration_test.go
deleted file mode 100644
index c4376ccaea5d..000000000000
--- a/pkg/detectors/exportsdk/exportsdk_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package exportsdk
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestExportSDK_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("EXPORTSDK")
- id := testSecrets.MustGetField("EXPORTSDK_TEMPLATE")
- inactiveSecret := testSecrets.MustGetField("EXPORTSDK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a exportsdk secret %s within exportsdk %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ExportSDK,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a exportsdk secret %s within exportsdk %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ExportSDK,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ExportSDK.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ExportSDK.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/exportsdk/exportsdk_test.go b/pkg/detectors/exportsdk/exportsdk_test.go
deleted file mode 100644
index 32b24ef7ec7d..000000000000
--- a/pkg/detectors/exportsdk/exportsdk_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package exportsdk
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Header"
- api_version: v1
- exportsdk_id: "ipwa96igr30chlcfr8xb7gack2xgfd7ov8zk"
- exportsdk_secret: "q6l59i_dd8w6gfvh--le8xasayvsufpvt4uh1pzmu07"
- base_url: "https://api.example.com/$api_version/example"
- query: ""
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "q6l59i_dd8w6gfvh--le8xasayvsufpvt4uh1pzmu07"
-)
-
-func TestExportSDK_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/extractorapi/extractorapi.go b/pkg/detectors/extractorapi/extractorapi.go
deleted file mode 100644
index 7c939df8678f..000000000000
--- a/pkg/detectors/extractorapi/extractorapi.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package extractorapi
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"extractorapi"}) + `\b([a-zA-Z-0-9]{40})\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{"extractorapi"}
-}
-
-// FromData will find and optionally verify ExtractorAPI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ExtractorAPI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://extractorapi.com/api/v1/extractor?apikey="+resMatch+"&url=example.com", nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ExtractorAPI
-}
-
-func (s Scanner) Description() string {
- return "ExtractorAPI is a service for extracting data from various sources. ExtractorAPI keys can be used to access and extract data from these sources."
-}
diff --git a/pkg/detectors/extractorapi/extractorapi_integration_test.go b/pkg/detectors/extractorapi/extractorapi_integration_test.go
deleted file mode 100644
index 2eb53e026273..000000000000
--- a/pkg/detectors/extractorapi/extractorapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package extractorapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestExtractorAPI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("EXTRACTORAPI")
- inactiveSecret := testSecrets.MustGetField("EXTRACTORAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a extractorapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ExtractorAPI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a extractorapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ExtractorAPI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ExtractorAPI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ExtractorAPI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/extractorapi/extractorapi_test.go b/pkg/detectors/extractorapi/extractorapi_test.go
deleted file mode 100644
index f52278bb1f85..000000000000
--- a/pkg/detectors/extractorapi/extractorapi_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package extractorapi
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- # Configuration File: config.yaml
- database:
- host: $DB_HOST
- port: $DB_PORT
- username: $DB_USERNAME
- password: $DB_PASS # IMPORTANT: Do not share this password publicly
-
- api:
- auth_type: "API-Key"
- in: "Path"
- api_version: v1
- extractorapi_secret: "jSCInysVesUIQ8vn7ZIQg3vKUCB8FgMnXTvJ4CKN"
- base_url: "https://api.example.com/$api_version/example"
- query: "apikey=$extractorapi_secret"
- response_code: 200
-
- # Notes:
- # - Remember to rotate the secret every 90 days.
- # - The above credentials should only be used in a secure environment.
- `
- secret = "jSCInysVesUIQ8vn7ZIQg3vKUCB8FgMnXTvJ4CKN"
-)
-
-func TestExtractorAPI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/facebookoauth/facebookoauth.go b/pkg/detectors/facebookoauth/facebookoauth.go
deleted file mode 100644
index 8dbfc13c0828..000000000000
--- a/pkg/detectors/facebookoauth/facebookoauth.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package facebookoauth
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- apiIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{"facebook"}) + `\b([0-9]{15,18})\b`) // not actually sure of the upper bound
- apiSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"facebook"}) + `\b([A-Za-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"facebook"}
-}
-
-// FromData will find and optionally verify FacebookOAuth secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- apiIdMatches := apiIdPat.FindAllStringSubmatch(dataStr, -1)
- apiSecretMatches := apiSecretPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, apiIdMatch := range apiIdMatches {
- apiIdRes := strings.TrimSpace(apiIdMatch[1])
-
- for _, apiSecretMatch := range apiSecretMatches {
- apiSecretRes := strings.TrimSpace(apiSecretMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FacebookOAuth,
- Redacted: apiIdRes,
- Raw: []byte(apiSecretRes),
- RawV2: []byte(apiIdRes + apiSecretRes),
- }
-
- if verify {
- // thanks https://stackoverflow.com/questions/15621471/validate-a-facebook-app-id-and-app-secret
- // https://stackoverflow.com/questions/24401241/how-to-get-a-facebook-access-token-using-appid-and-app-secret-without-any-login
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://graph.facebook.com/me?access_token=%s|%s", apiIdRes, apiSecretRes), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FacebookOAuth
-}
-
-func (s Scanner) Description() string {
- return "Facebook OAuth tokens are used to authenticate users and provide access to Facebook's API services."
-}
diff --git a/pkg/detectors/facebookoauth/facebookoauth_integration_test.go b/pkg/detectors/facebookoauth/facebookoauth_integration_test.go
deleted file mode 100644
index b264e2230ab3..000000000000
--- a/pkg/detectors/facebookoauth/facebookoauth_integration_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package facebookoauth
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFacebookOAuth_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- appId := testSecrets.MustGetField("FACEBOOK_APP_ID")
- appSecret := testSecrets.MustGetField("FACEBOOK_APP_SECRET")
- inactiveAppSecret := testSecrets.MustGetField("FACEBOOK_APP_SECRET_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a facebook appid %s and secret %s within", appId, appSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FacebookOAuth,
- Redacted: appId,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a facebook appid %s and secret %s within but not valid", inactiveAppSecret, appId)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FacebookOAuth,
- Redacted: appId,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FacebookOAuth.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("FacebookOAuth.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/facebookoauth/facebookoauth_test.go b/pkg/detectors/facebookoauth/facebookoauth_test.go
deleted file mode 100644
index 15ef0319ac5f..000000000000
--- a/pkg/detectors/facebookoauth/facebookoauth_test.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package facebookoauth
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Facebook",
- "type": "Detector",
- "api": true,
- "authentication_type": "OAuth",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "facebook_appid": "5295912532069628",
- "facebook_secret": "rw6rTIk14bOEW84MkNbLVqVbrLJugJo7"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "5295912532069628rw6rTIk14bOEW84MkNbLVqVbrLJugJo7"
-)
-
-func TestFacebookOAuth_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/faceplusplus/faceplusplus.go b/pkg/detectors/faceplusplus/faceplusplus.go
deleted file mode 100644
index 696d62e305b7..000000000000
--- a/pkg/detectors/faceplusplus/faceplusplus.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package faceplusplus
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"faceplusplus"}) + `\b([0-9a-zA-Z_-]{32})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"faceplusplus"}) + `\b([0-9a-zA-Z_-]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"faceplusplus"}
-}
-
-// FromData will find and optionally verify Faceplusplus secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, secretMatch := range secretMatches {
- resSecret := strings.TrimSpace(secretMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FacePlusPlus,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resSecret),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("https://api-us.faceplusplus.com/facepp/v3/faceset/getfacesets?api_key=%s&api_secret=%s", resMatch, resSecret), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FacePlusPlus
-}
-
-func (s Scanner) Description() string {
- return "Face++ is a facial recognition service that provides APIs for detecting and analyzing faces. Face++ API keys and secrets can be used to access and manipulate these services."
-}
diff --git a/pkg/detectors/faceplusplus/faceplusplus_integration_test.go b/pkg/detectors/faceplusplus/faceplusplus_integration_test.go
deleted file mode 100644
index 9276cb70323f..000000000000
--- a/pkg/detectors/faceplusplus/faceplusplus_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package faceplusplus
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFaceplusplus_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- key := testSecrets.MustGetField("FACEPLUSPLUS_KEY")
- secret := testSecrets.MustGetField("FACEPLUSPLUS_SECRET")
- inactiveSecret := testSecrets.MustGetField("FACEPLUSPLUS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a faceplusplus key %s within faceplusplus secret %s", key, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FacePlusPlus,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a facepluspluskey %s within faceplusplussecret %s but not valid", key, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FacePlusPlus,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Faceplusplus.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Faceplusplus.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/faceplusplus/faceplusplus_test.go b/pkg/detectors/faceplusplus/faceplusplus_test.go
deleted file mode 100644
index 2d6ea980f877..000000000000
--- a/pkg/detectors/faceplusplus/faceplusplus_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package faceplusplus
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FacePlusPlus",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "faceplusplus_id": "ipScAHzUxOS2CQ3JwTdIDG1ClxZl_iVH",
- "faceplusplus_secret": "Qomsw0IQtp3iz1jlxAqQJO5afpbeEeAh"
- },
- "expected_response": "200",
- "method": "POST",
- "deprecated": false
- }]`
- secrets = []string{
- // TODO: Add logic to avoid verification when key and id is same because the regex is same for both
- "Qomsw0IQtp3iz1jlxAqQJO5afpbeEeAhQomsw0IQtp3iz1jlxAqQJO5afpbeEeAh",
- "ipScAHzUxOS2CQ3JwTdIDG1ClxZl_iVHQomsw0IQtp3iz1jlxAqQJO5afpbeEeAh",
- "Qomsw0IQtp3iz1jlxAqQJO5afpbeEeAhipScAHzUxOS2CQ3JwTdIDG1ClxZl_iVH",
- "ipScAHzUxOS2CQ3JwTdIDG1ClxZl_iVHipScAHzUxOS2CQ3JwTdIDG1ClxZl_iVH",
- }
-)
-
-func TestFacePlusPlus_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/falsepositives.go b/pkg/detectors/falsepositives.go
deleted file mode 100644
index 316d1d752163..000000000000
--- a/pkg/detectors/falsepositives.go
+++ /dev/null
@@ -1,201 +0,0 @@
-package detectors
-
-import (
- _ "embed"
- "fmt"
- "math"
- "strings"
- "unicode"
- "unicode/utf8"
-
- ahocorasick "github.com/BobuSumisu/aho-corasick"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
-)
-
-var (
- DefaultFalsePositives = map[FalsePositive]struct{}{
- "example": {}, "xxxxxx": {}, "aaaaaa": {}, "abcde": {}, "00000": {}, "sample": {}, "*****": {},
- }
- UuidFalsePositives map[FalsePositive]struct{}
-)
-
-type FalsePositive string
-
-type CustomFalsePositiveChecker interface {
- // IsFalsePositive returns two values:
- // 1. Whether the result is a false positive.
- // 2. If #1 is `true`, the reason why.
- IsFalsePositive(result Result) (bool, string)
-}
-
-var (
- filter *ahocorasick.Trie
-
- //go:embed "fp_badlist.txt"
- badList []byte
- //go:embed "fp_words.txt"
- wordList []byte
- //go:embed "fp_programmingbooks.txt"
- programmingBookWords []byte
- //go:embed "fp_uuids.txt"
- uuidList []byte
-)
-
-func init() {
- // Populate trie.
- builder := ahocorasick.NewTrieBuilder()
-
- wordList := bytesToCleanWordList(wordList)
- builder.AddStrings(wordList)
-
- badList := bytesToCleanWordList(badList)
- builder.AddStrings(badList)
-
- programmingBookWords := bytesToCleanWordList(programmingBookWords)
- builder.AddStrings(programmingBookWords)
-
- uuidList := bytesToCleanWordList(uuidList)
- builder.AddStrings(uuidList)
-
- filter = builder.Build()
-
- // Populate custom FalsePositive list
- UuidFalsePositives = make(map[FalsePositive]struct{}, len(uuidList))
- for _, uuid := range uuidList {
- UuidFalsePositives[FalsePositive(uuid)] = struct{}{}
- }
-}
-
-func GetFalsePositiveCheck(detector Detector) func(Result) (bool, string) {
- checker, ok := detector.(CustomFalsePositiveChecker)
- if ok {
- return checker.IsFalsePositive
- }
-
- return func(res Result) (bool, string) {
- return IsKnownFalsePositive(string(res.Raw), DefaultFalsePositives, true)
- }
-}
-
-// IsKnownFalsePositive returns whether a finding is (likely) a known false positive, and the reason for the detection.
-//
-// Currently, this includes: english word in key or matches common example patterns.
-// Only the secret key material should be passed into this function
-func IsKnownFalsePositive(match string, falsePositives map[FalsePositive]struct{}, wordCheck bool) (bool, string) {
- if !utf8.ValidString(match) {
- return true, "invalid utf8"
- }
- lower := strings.ToLower(match)
-
- if _, exists := falsePositives[FalsePositive(lower)]; exists {
- return true, "matches term: " + lower
- }
-
- for fp := range falsePositives {
- fps := string(fp)
- if strings.Contains(lower, fps) {
- return true, "contains term: " + fps
- }
- }
-
- if wordCheck {
- if m := filter.MatchFirstString(lower); m != nil {
- return true, "matches wordlist: " + m.MatchString()
- }
- }
-
- return false, ""
-}
-
-func HasDigit(key string) bool {
- for _, ch := range key {
- if unicode.IsDigit(ch) {
- return true
- }
- }
-
- return false
-}
-
-func bytesToCleanWordList(data []byte) []string {
- words := make(map[string]struct{})
- for _, word := range strings.Split(string(data), "\n") {
- if strings.TrimSpace(word) != "" {
- words[strings.TrimSpace(strings.ToLower(word))] = struct{}{}
- }
- }
-
- wordList := make([]string, 0, len(words))
- for word := range words {
- wordList = append(wordList, word)
- }
- return wordList
-}
-
-func StringShannonEntropy(input string) float64 {
- chars := make(map[rune]float64)
- inverseTotal := 1 / float64(len(input)) // precompute the inverse
-
- for _, char := range input {
- chars[char]++
- }
-
- entropy := 0.0
- for _, count := range chars {
- probability := count * inverseTotal
- entropy += probability * math.Log2(probability)
- }
-
- return -entropy
-}
-
-// FilterResultsWithEntropy filters out determinately unverified results that have a shannon entropy below the given value.
-func FilterResultsWithEntropy(ctx context.Context, results []Result, entropy float64, shouldLog bool) []Result {
- var filteredResults []Result
- for _, result := range results {
- if !result.Verified {
- if result.Raw != nil {
- if StringShannonEntropy(string(result.Raw)) >= entropy {
- filteredResults = append(filteredResults, result)
- } else {
- if shouldLog {
- ctx.Logger().Info("Filtered out result with low entropy", "result", result)
- }
- }
- } else {
- filteredResults = append(filteredResults, result)
- }
- } else {
- filteredResults = append(filteredResults, result)
- }
- }
- return filteredResults
-}
-
-// FilterKnownFalsePositives filters out known false positives from the results.
-func FilterKnownFalsePositives(ctx context.Context, detector Detector, results []Result) []Result {
- var filteredResults []Result
-
- isFalsePositive := GetFalsePositiveCheck(detector)
-
- for _, result := range results {
- if len(result.Raw) == 0 {
- ctx.Logger().Error(fmt.Errorf("empty raw"), "Skipping result: invalid")
- continue
- }
-
- if result.Verified {
- filteredResults = append(filteredResults, result)
- continue
- }
-
- if isFp, reason := isFalsePositive(result); isFp {
- ctx.Logger().V(4).Info("Skipping result: false positive", "result", string(result.Raw), "reason", reason)
- continue
- }
- filteredResults = append(filteredResults, result)
- }
-
- return filteredResults
-}
diff --git a/pkg/detectors/falsepositives_test.go b/pkg/detectors/falsepositives_test.go
deleted file mode 100644
index 7d3cedafc479..000000000000
--- a/pkg/detectors/falsepositives_test.go
+++ /dev/null
@@ -1,191 +0,0 @@
-package detectors
-
-import (
- "context"
- _ "embed"
- "testing"
-
- "github.com/stretchr/testify/assert"
-
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type fakeDetector struct{}
-type customFalsePositiveChecker struct{ fakeDetector }
-
-func (d fakeDetector) FromData(ctx context.Context, verify bool, data []byte) ([]Result, error) {
- return nil, nil
-}
-
-func (d fakeDetector) Keywords() []string {
- return nil
-}
-
-func (d fakeDetector) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType(0)
-}
-
-func (f fakeDetector) Description() string { return "" }
-
-func (d customFalsePositiveChecker) IsFalsePositive(result Result) (bool, string) {
- return IsKnownFalsePositive(string(result.Raw), map[FalsePositive]struct{}{"a specific magic string": {}}, false)
-}
-
-func TestFilterKnownFalsePositives_DefaultLogic(t *testing.T) {
- results := []Result{
- {Raw: []byte("00000")}, // "default" false positive list
- {Raw: []byte("number")}, // from wordlist
- // from uuid list
- {Raw: []byte("00000000-0000-0000-0000-000000000000")},
- {Raw: []byte("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")},
- // real secrets
- {Raw: []byte("hga8adshla3434g")},
- {Raw: []byte("f795f7db-2dfe-4095-96f3-8f8370c735f9")},
- }
- expected := []Result{
- {Raw: []byte("hga8adshla3434g")},
- {Raw: []byte("f795f7db-2dfe-4095-96f3-8f8370c735f9")},
- }
- filtered := FilterKnownFalsePositives(logContext.Background(), fakeDetector{}, results)
- assert.ElementsMatch(t, expected, filtered)
-}
-
-func TestFilterKnownFalsePositives_CustomLogic(t *testing.T) {
- results := []Result{
- {Raw: []byte("a specific magic string")}, // specific target
- {Raw: []byte("00000")}, // "default" false positive list
- {Raw: []byte("number")}, // from wordlist
- {Raw: []byte("hga8adshla3434g")}, // real secret
- }
- expected := []Result{
- {Raw: []byte("00000")},
- {Raw: []byte("number")},
- {Raw: []byte("hga8adshla3434g")},
- }
- filtered := FilterKnownFalsePositives(logContext.Background(), customFalsePositiveChecker{}, results)
- assert.ElementsMatch(t, expected, filtered)
-}
-
-func TestIsFalsePositive(t *testing.T) {
- type args struct {
- match string
- falsePositives map[FalsePositive]struct{}
- useWordlist bool
- }
- tests := []struct {
- name string
- args args
- want bool
- }{
- {
- name: "fp",
- args: args{
- match: "example",
- falsePositives: DefaultFalsePositives,
- useWordlist: false,
- },
- want: true,
- },
- {
- name: "fp - in wordlist",
- args: args{
- match: "sdfdsfprivatesfsdfd",
- falsePositives: DefaultFalsePositives,
- useWordlist: true,
- },
- want: true,
- },
- {
- name: "fp - not in wordlist",
- args: args{
- match: "sdfdsfsfsdfd",
- falsePositives: DefaultFalsePositives,
- useWordlist: true,
- },
- want: false,
- },
- {
- name: "not fp",
- args: args{
- match: "notafp123",
- falsePositives: DefaultFalsePositives,
- useWordlist: false,
- },
- want: false,
- },
- {
- name: "fp - in wordlist exact match",
- args: args{
- match: "private",
- falsePositives: DefaultFalsePositives,
- useWordlist: true,
- },
- want: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got, _ := IsKnownFalsePositive(tt.args.match, tt.args.falsePositives, tt.args.useWordlist); got != tt.want {
- t.Errorf("IsKnownFalsePositive() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func TestStringShannonEntropy(t *testing.T) {
- type args struct {
- input string
- }
- tests := []struct {
- name string
- args args
- want float64
- }{
- {
- name: "entropy 1",
- args: args{
- input: "aaaaaaaaaaaaaaaaaaaaaaaaaaaa",
- },
- want: 0,
- },
- {
- name: "entropy 2",
- args: args{
- input: "aaaaaaaaaaaaaaaaaaaaaaaaaaab",
- },
- want: 0.22,
- },
- {
- name: "entropy 3",
- args: args{
- input: "aaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaab",
- },
- want: 0.22,
- },
- {
- name: "empty",
- args: args{
- input: "",
- },
- want: 0.0,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := StringShannonEntropy(tt.args.input)
- if len(tt.args.input) > 0 && tt.want != 0 {
- assert.InEpsilon(t, tt.want, got, 0.1)
- } else {
- assert.Equal(t, tt.want, got)
- }
- })
- }
-}
-
-func BenchmarkDefaultIsKnownFalsePositive(b *testing.B) {
- for i := 0; i < b.N; i++ {
- // Use a string that won't be found in any dictionary for the worst case check.
- IsKnownFalsePositive("aoeuaoeuaoeuaoeuaoeuaoeu", DefaultFalsePositives, true)
- }
-}
diff --git a/pkg/detectors/fastforex/fastforex.go b/pkg/detectors/fastforex/fastforex.go
deleted file mode 100644
index d2c4add4935c..000000000000
--- a/pkg/detectors/fastforex/fastforex.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package fastforex
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"fastforex"}) + `\b([a-z0-9-]{28})\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{"fastforex"}
-}
-
-// FromData will find and optionally verify FastForex secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FastForex,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.fastforex.io/fetch-all?api_key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FastForex
-}
-
-func (s Scanner) Description() string {
- return "FastForex provides foreign exchange rate data. FastForex API keys can be used to access and retrieve this data."
-}
diff --git a/pkg/detectors/fastforex/fastforex_integration_test.go b/pkg/detectors/fastforex/fastforex_integration_test.go
deleted file mode 100644
index c3ff9dfc6735..000000000000
--- a/pkg/detectors/fastforex/fastforex_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package fastforex
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFastForex_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FASTFOREX")
- inactiveSecret := testSecrets.MustGetField("FASTFOREX_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fastforex secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FastForex,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fastforex secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FastForex,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FastForex.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("FastForex.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/fastforex/fastforex_test.go b/pkg/detectors/fastforex/fastforex_test.go
deleted file mode 100644
index 9a4f77250a29..000000000000
--- a/pkg/detectors/fastforex/fastforex_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package fastforex
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FastForex",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "fastforex_secret": "jk-qatdz1xcgoz3yssqexstefbtq"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "jk-qatdz1xcgoz3yssqexstefbtq"
-)
-
-func TestFastForex_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/fastlypersonaltoken/fastlypersonaltoken.go b/pkg/detectors/fastlypersonaltoken/fastlypersonaltoken.go
deleted file mode 100644
index 9ab6559c0186..000000000000
--- a/pkg/detectors/fastlypersonaltoken/fastlypersonaltoken.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package fastlypersonaltoken
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"fastly"}) + `\b([A-Za-z0-9_-]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"fastly"}
-}
-
-type token struct {
- TokenID string `json:"id"`
- UserID string `json:"user_id"`
- ExpiresAt string `json:"expires_at"`
- Scope string `json:"scope"`
-}
-
-// FromData will find and optionally verify FastlyPersonalToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueMatches = make(map[string]struct{})
-
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[matches[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FastlyPersonalToken,
- Raw: []byte(match),
- }
-
- if verify {
- extraData, verified, verificationErr := verifyFastlyApiToken(ctx, match)
- s1.Verified = verified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, match)
-
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{
- "key": match,
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FastlyPersonalToken
-}
-
-func (s Scanner) Description() string {
- return "Fastly is a content delivery network (CDN) and cloud service provider. Fastly personal tokens can be used to authenticate API requests to Fastly services."
-}
-
-func verifyFastlyApiToken(ctx context.Context, apiToken string) (map[string]string, bool, error) {
- // api-docs: https://www.fastly.com/documentation/reference/api/auth-tokens/user/
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.fastly.com/tokens/self", nil)
- if err != nil {
- return nil, false, err
- }
-
- // add api key in the header
- req.Header.Add("Fastly-Key", apiToken)
- resp, err := client.Do(req)
- if err != nil {
- return nil, false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- var self token
- if err = json.NewDecoder(resp.Body).Decode(&self); err != nil {
- return nil, false, err
- }
-
- // capture token details in the map
- extraData := map[string]string{
- // token id is the alphanumeric string uniquely identifying a token
- "token_id": self.TokenID,
- // user id is the alphanumeric string uniquely identifying the user
- "user_id": self.UserID,
- // expires at is time-stamp (UTC) of when the token will expire
- "token_expires_at": self.ExpiresAt,
- // token scope is space-delimited list of authorization scope of the token
- "token_scope": self.Scope,
- }
-
- // if expires at is empty which mean token is set to never expire, add 'Never' as the value
- if extraData["token_expires_at"] == "" {
- extraData["token_expires_at"] = "never"
- }
-
- return extraData, true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- // as per fastly documentation: An HTTP 401 response is returned on an expired token. An HTTP 403 response is returned on an invalid access token.
- return nil, false, nil
- default:
- return nil, false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
-
- }
-}
diff --git a/pkg/detectors/fastlypersonaltoken/fastlypersonaltoken_integration_test.go b/pkg/detectors/fastlypersonaltoken/fastlypersonaltoken_integration_test.go
deleted file mode 100644
index 3bbeee3ac6b9..000000000000
--- a/pkg/detectors/fastlypersonaltoken/fastlypersonaltoken_integration_test.go
+++ /dev/null
@@ -1,127 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package fastlypersonaltoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFastlyPersonalToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FASTLYPERSONALTOKEN_TOKEN")
- inactiveSecret := testSecrets.MustGetField("FASTLYPERSONALTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: ctx,
- data: []byte(fmt.Sprintf("You can find a fastlypersonaltoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FastlyPersonalToken,
- Verified: true,
- ExtraData: map[string]string{
- "token_id": "2ICO7ArmhY8OMiiOyNpXfc",
- "user_id": "7anDA1ct17E8pkFAE0tJkk",
- "token_expires_at": "never",
- "token_scope": "global:read global",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fastlypersonaltoken secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FastlyPersonalToken,
- Verified: false,
- ExtraData: nil,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FastlyPersonalToken.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])
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("FastlyPersonalToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/fastlypersonaltoken/fastlypersonaltoken_test.go b/pkg/detectors/fastlypersonaltoken/fastlypersonaltoken_test.go
deleted file mode 100644
index 0d14ddfeccd3..000000000000
--- a/pkg/detectors/fastlypersonaltoken/fastlypersonaltoken_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package fastlypersonaltoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- // example picked from: https://github.com/ryan-miller/learn-go-with-tests/blob/181467c9e512f7e68d3f3cbcea89f0050982416c/fastly/users.go#L22
- validPattern = `
- // headers and header values
- const fastlyKeyToken string = "Fastly-Key"
- const fastlyKey string = "TVAWji0p7uDI6OP9DyWvmV-vgoUoXIuf"
- const contentTypeToken string = "Content-Type"
- const appJsonContentType = "application/json"`
-
- validPatternToken = "TVAWji0p7uDI6OP9DyWvmV-vgoUoXIuf"
-
- invalidPattern = `
- // headers and header values
- const fastlyKeyToken string = "Fastly-Key"
- const fastlyKey string = "$FASTLY_KEY"
- const contentTypeToken string = "Content-Type"
- const appJsonContentType = "application/json"`
-)
-
-func TestFastlyPersonalToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{validPatternToken},
- },
- {
- name: "invalid pattern",
- input: invalidPattern,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/feedier/feedier.go b/pkg/detectors/feedier/feedier.go
deleted file mode 100644
index da949eb9b81f..000000000000
--- a/pkg/detectors/feedier/feedier.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package feedier
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"feedier"}) + `\b([a-z0-9A-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"feedier"}
-}
-
-// FromData will find and optionally verify Feedier secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Feedier,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.feedier.com/v1/carriers", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Feedier
-}
-
-func (s Scanner) Description() string {
- return "Feedier is a feedback management platform that allows businesses to collect and analyze customer feedback. Feedier API keys can be used to access and manage feedback data."
-}
diff --git a/pkg/detectors/feedier/feedier_integration_test.go b/pkg/detectors/feedier/feedier_integration_test.go
deleted file mode 100644
index dc17603b03d0..000000000000
--- a/pkg/detectors/feedier/feedier_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package feedier
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFeedier_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FEEDIER_TOKEN")
- inactiveSecret := testSecrets.MustGetField("FEEDIER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a feedier secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Feedier,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a feedier secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Feedier,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Feedier.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Feedier.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/feedier/feedier_test.go b/pkg/detectors/feedier/feedier_test.go
deleted file mode 100644
index 0ef8c7b75ce0..000000000000
--- a/pkg/detectors/feedier/feedier_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package feedier
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Feedier",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "feedier_secret": "kZ581ej1fDjtvE8iXNcgFJ8V2t0Lfv1d"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "kZ581ej1fDjtvE8iXNcgFJ8V2t0Lfv1d"
-)
-
-func TestFeedier_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/fetchrss/fetchrss.go b/pkg/detectors/fetchrss/fetchrss.go
deleted file mode 100644
index ff6d319ba0ea..000000000000
--- a/pkg/detectors/fetchrss/fetchrss.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package fetchrss
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"fetchrss"}) + `\b([a-zA-Z0-9.]{40})\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{"fetchrss"}
-}
-
-// FromData will find and optionally verify Fetchrss secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for token := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Fetchrss,
- Raw: []byte(token),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- verified, verificationErr := verifyToken(ctx, client, token)
- s1.Verified = verified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyToken(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://fetchrss.com/api/v1/feed/list?auth="+token, nil)
- if err != nil {
- return false, err
- }
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- // The API seems to always return a 200 status code.
- // See: https://fetchrss.com/developers
- if res.StatusCode != http.StatusOK {
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-
- var apiRes response
- if err := json.NewDecoder(res.Body).Decode(&apiRes); err != nil {
- return false, err
- }
-
- if apiRes.Success {
- // The key is valid.
- return true, nil
- } else if apiRes.Error.Code == 401 {
- // The key is invalid.
- return false, nil
- } else {
- return false, fmt.Errorf("unexpected error: [code=%d, message=%s]", apiRes.Error.Code, apiRes.Error.Message)
- }
-}
-
-type response struct {
- Success bool `json:"success"`
- Error struct {
- Message string `json:"message"`
- Code int `json:"code"`
- } `json:"error"`
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Fetchrss
-}
-
-func (s Scanner) Description() string {
- return "FetchRSS is a service used to convert web content into RSS feeds. FetchRSS API keys can be used to manage and access these feeds."
-}
diff --git a/pkg/detectors/fetchrss/fetchrss_integration_test.go b/pkg/detectors/fetchrss/fetchrss_integration_test.go
deleted file mode 100644
index e6ef2f9dd0d3..000000000000
--- a/pkg/detectors/fetchrss/fetchrss_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package fetchrss
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFetchrss_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FETCHRSS_TOKEN")
- inactiveSecret := testSecrets.MustGetField("FETCHRSS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fetchrss secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Fetchrss,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fetchrss secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Fetchrss,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Fetchrss.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Fetchrss.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/fetchrss/fetchrss_test.go b/pkg/detectors/fetchrss/fetchrss_test.go
deleted file mode 100644
index 31d461c6196a..000000000000
--- a/pkg/detectors/fetchrss/fetchrss_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package fetchrss
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FetchRSS",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "fetchrss_secret": "x3lljmW2KHoljMrcFSTN5nWWAvDjwdQA0ed0QmHL"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "x3lljmW2KHoljMrcFSTN5nWWAvDjwdQA0ed0QmHL"
-)
-
-func TestFetchRSS_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/fibery/fibery.go b/pkg/detectors/fibery/fibery.go
deleted file mode 100644
index adc981abef92..000000000000
--- a/pkg/detectors/fibery/fibery.go
+++ /dev/null
@@ -1,117 +0,0 @@
-package fibery
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"fibery"}) + `\b([0-9a-f]{8}\.[0-9a-f]{35})\b`)
- domainPat = regexp.MustCompile(`(?:https?:\/\/)?([a-zA-Z0-9-]{1,63})\.fibery\.io(?:\/.*)?`)
-)
-
-// 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{".fibery.io"}
-}
-
-// Description returns a description for the result being detected
-func (s Scanner) Description() string {
- return "Fibery is a work management platform that combines various tools for project management, knowledge management, and software development. Fibery API tokens can be used to access and modify data within a Fibery workspace."
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Fibery secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueSecrets := make(map[string]struct{})
- uniqueDomains := make(map[string]struct{})
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueSecrets[match[1]] = struct{}{}
- }
- for _, match := range domainPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueDomains[match[1]] = struct{}{}
- }
-
- for secret := range uniqueSecrets {
- for domain := range uniqueDomains {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Fibery,
- Raw: []byte(secret),
- }
-
- if verify {
- isVerified, verificationErr := verifyMatch(ctx, s.getClient(), secret, domain)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, secret, domain)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, secret, domain string) (bool, error) {
- timeout := 10 * time.Second
- client.Timeout = timeout
- url := fmt.Sprintf("https://%s.fibery.io/api/commands", domain)
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Token %s", secret))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Fibery
-}
diff --git a/pkg/detectors/fibery/fibery_integration_test.go b/pkg/detectors/fibery/fibery_integration_test.go
deleted file mode 100644
index e1c6d46af58a..000000000000
--- a/pkg/detectors/fibery/fibery_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package fibery
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFibery_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FIBERY_SECRET")
- domain := testSecrets.MustGetField("FIBERY_DOMAIN")
- inactiveSecret := testSecrets.MustGetField("FIBERY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fibery secret %s within fibery domain %s ", secret, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Fibery,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fibery secret %s within fibery domain %s but not valid", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Fibery,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Fibery.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Fibery.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/fibery/fibery_test.go b/pkg/detectors/fibery/fibery_test.go
deleted file mode 100644
index e918400a3dbf..000000000000
--- a/pkg/detectors/fibery/fibery_test.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package fibery
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Fibery",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://detector.fibery.io/example",
- "domain": "nonprod",
- "test_secrets": {
- "fibery_secret": "42b2eda8.3fe6b086bb21be7e3548368626d01aaf2cd"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "42b2eda8.3fe6b086bb21be7e3548368626d01aaf2cd"
-)
-
-func TestFibery_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalaccesstoken.go b/pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalaccesstoken.go
deleted file mode 100644
index 90b52133abab..000000000000
--- a/pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalaccesstoken.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package figmapersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-func (Scanner) Version() int { return 1 }
-
-var (
- defaultClient = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"figma"}) + `\b([0-9]{6}-[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\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{"figma"}
-}
-
-// FromData will find and optionally verify FigmaPersonalAccessToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,
- Raw: []byte(resMatch),
- ExtraData: map[string]string{
- "version": fmt.Sprintf("%d", s.Version()),
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.figma.com/v1/me", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-Figma-Token", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- } else if res.StatusCode != 403 {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{"token": resMatch}
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FigmaPersonalAccessToken
-}
-
-func (s Scanner) Description() string {
- return "Figma is a web-based design tool. Personal Access Tokens can be used to access and modify design files and other resources."
-}
diff --git a/pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalaccesstoken_test.go b/pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalaccesstoken_test.go
deleted file mode 100644
index 7b4e4384ced7..000000000000
--- a/pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalaccesstoken_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package figmapersonalaccesstoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Figma",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "figma_secret": "647501-6p71dd66-3k6s-un9a-0ri0-0ypi87cz3rmx"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "647501-6p71dd66-3k6s-un9a-0ri0-0ypi87cz3rmx"
-)
-
-func TestFigmaPersonalAccessToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalacesstoken_integration_test.go b/pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalacesstoken_integration_test.go
deleted file mode 100644
index 86132063cad6..000000000000
--- a/pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalacesstoken_integration_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package figmapersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFigmaPersonalAccessToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FIGMAPERSONALACCESSTOKEN_TOKEN")
- inactiveSecret := testSecrets.MustGetField("FIGMAPERSONALACCESSTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a figmapersonalaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,
- Verified: true,
- ExtraData: map[string]string{
- "version": "1",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a figmapersonalaccesstoken secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,
- Verified: false,
- ExtraData: map[string]string{
- "version": "1",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a figmapersonalaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,
- Verified: false,
- ExtraData: map[string]string{
- "version": "1",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a figmapersonalaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,
- Verified: false,
- ExtraData: map[string]string{
- "version": "1",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FigmaPersonalAccessToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("FigmaPersonalAccessToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_integration_test.go b/pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_integration_test.go
deleted file mode 100644
index ae485e7b9dbf..000000000000
--- a/pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_integration_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package figmapersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFigmaPersonalAccessToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FIGMAPERSONALACCESSTOKEN_V2_TOKEN")
- inactiveSecret := testSecrets.MustGetField("FIGMAPERSONALACCESSTOKEN_V2_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a figmapersonalaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,
- Verified: true,
- ExtraData: map[string]string{
- "version": "2",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a figmapersonalaccesstoken secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,
- Verified: false,
- ExtraData: map[string]string{
- "version": "2",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a figmapersonalaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,
- Verified: false,
- ExtraData: map[string]string{
- "version": "2",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a figmapersonalaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,
- Verified: false,
- ExtraData: map[string]string{
- "version": "2",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FigmaPersonalAccessToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("FigmaPersonalAccessToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_v2.go b/pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_v2.go
deleted file mode 100644
index 969d716e0990..000000000000
--- a/pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_v2.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package figmapersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-func (Scanner) Version() int { return 2 }
-
-var (
- defaultClient = common.SaneHttpClient()
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"figma"}) + `\b(fig[d|((u|o)(r|h)?)]_[a-z0-9A-Z_-]{40})\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{"figma"}
-}
-
-// Description returns a description for the result being detected.
-func (s Scanner) Description() string {
- return "Figma is a collaborative interface design tool. Figma Personal Access Tokens can be used to access and manipulate design files and other resources on behalf of a user."
-}
-
-// FromData will find and optionally verify FigmaPersonalAccessToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,
- Raw: []byte(resMatch),
- ExtraData: map[string]string{
- "version": fmt.Sprintf("%d", s.Version()),
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.figma.com/v1/me", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-Figma-Token", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- } else if res.StatusCode != 403 {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{"token": resMatch}
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FigmaPersonalAccessToken
-}
diff --git a/pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_v2_test.go b/pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_v2_test.go
deleted file mode 100644
index 82ccc0e51f5b..000000000000
--- a/pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_v2_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package figmapersonalaccesstoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Figma",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "figma_secret": "figr_EZe7plhYvN92IyiDCjkvTcbNVZsuRVpDcHOwNNP1"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "figr_EZe7plhYvN92IyiDCjkvTcbNVZsuRVpDcHOwNNP1"
-)
-
-func TestFigmaPersonalAccessToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/fileio/fileio.go b/pkg/detectors/fileio/fileio.go
deleted file mode 100644
index 73c9338cd248..000000000000
--- a/pkg/detectors/fileio/fileio.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package fileio
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"fileio"}) + `\b([A-Z0-9.-]{39})\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{"fileio"}
-}
-
-// FromData will find and optionally verify FileIO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FileIO,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://file.io/", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err == nil {
- isJson := json.Valid(bodyBytes)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if isJson {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FileIO
-}
-
-func (s Scanner) Description() string {
- return "FileIO is a service for temporary file sharing. The detected key can be used to access and manage shared files."
-}
diff --git a/pkg/detectors/fileio/fileio_integration_test.go b/pkg/detectors/fileio/fileio_integration_test.go
deleted file mode 100644
index c8c151863407..000000000000
--- a/pkg/detectors/fileio/fileio_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package fileio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFileIO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FILEIO")
- inactiveSecret := testSecrets.MustGetField("FILEIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fileio secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FileIO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fileio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FileIO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FileIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("FileIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/fileio/fileio_test.go b/pkg/detectors/fileio/fileio_test.go
deleted file mode 100644
index 48f3737e9d69..000000000000
--- a/pkg/detectors/fileio/fileio_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package fileio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Fileio",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "fileio_secret": "4N4VTAX5KCE0L6R56HS9778HVC2.KH83JBNN7F3"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "4N4VTAX5KCE0L6R56HS9778HVC2.KH83JBNN7F3"
-)
-
-func TestFileIO_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/finage/finage.go b/pkg/detectors/finage/finage.go
deleted file mode 100644
index baf9aa487101..000000000000
--- a/pkg/detectors/finage/finage.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package finage
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(API_KEY[0-9A-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"finage"}
-}
-
-// FromData will find and optionally verify Finage secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Finage,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.finage.co.uk/symbol-list/crypto?apikey=%s", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Finage
-}
-
-func (s Scanner) Description() string {
- return "Finage provides financial data APIs for stocks, forex, and cryptocurrencies. Finage API keys can be used to access and retrieve financial data."
-}
diff --git a/pkg/detectors/finage/finage_integration_test.go b/pkg/detectors/finage/finage_integration_test.go
deleted file mode 100644
index 6f3d2168feaf..000000000000
--- a/pkg/detectors/finage/finage_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package finage
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFinage_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FINAGE")
- inactiveSecret := testSecrets.MustGetField("FINAGE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a finage secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Finage,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a finage secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Finage,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Finage.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Finage.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/finage/finage_test.go b/pkg/detectors/finage/finage_test.go
deleted file mode 100644
index 1d8e7f8379c7..000000000000
--- a/pkg/detectors/finage/finage_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package finage
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Finage",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "secret": "API_KEYN2B1NFN5CP6CK5BJHY8B15YF535TP681"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "API_KEYN2B1NFN5CP6CK5BJHY8B15YF535TP681"
-)
-
-func TestFinage_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/financialmodelingprep/financialmodelingprep.go b/pkg/detectors/financialmodelingprep/financialmodelingprep.go
deleted file mode 100644
index 1aa7593697c6..000000000000
--- a/pkg/detectors/financialmodelingprep/financialmodelingprep.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package financialmodelingprep
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"financialmodelingprep"}) + `\b([a-zA-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"financialmodelingprep"}
-}
-
-// FromData will find and optionally verify FinancialModelingPrep secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FinancialModelingPrep,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://financialmodelingprep.com/api/v3/financial-statement-symbol-lists?apikey=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- bodyString := string(bodyBytes)
- if err == nil {
- // valid response should be an array of currencies
- // error response is in json
- validResponse := strings.Contains(bodyString, `[ "`)
-
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FinancialModelingPrep
-}
-
-func (s Scanner) Description() string {
- return "FinancialModelingPrep provides financial data APIs. The API keys can be used to access financial data and related services."
-}
diff --git a/pkg/detectors/financialmodelingprep/financialmodelingprep_integration_test.go b/pkg/detectors/financialmodelingprep/financialmodelingprep_integration_test.go
deleted file mode 100644
index 43cc8366ebec..000000000000
--- a/pkg/detectors/financialmodelingprep/financialmodelingprep_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package financialmodelingprep
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFinancialModelingPrep_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FINANCIALMODELINGPREP")
- inactiveSecret := testSecrets.MustGetField("FINANCIALMODELINGPREP_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a financialmodelingprep secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FinancialModelingPrep,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a financialmodelingprep secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FinancialModelingPrep,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FinancialModelingPrep.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("FinancialModelingPrep.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/financialmodelingprep/financialmodelingprep_test.go b/pkg/detectors/financialmodelingprep/financialmodelingprep_test.go
deleted file mode 100644
index 18fcbc1f5f23..000000000000
--- a/pkg/detectors/financialmodelingprep/financialmodelingprep_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package financialmodelingprep
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Financial Modeling Prep",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "financialmodelingprep_secret": "WXEUwkx44VjTRlunqyncJOCDeszMoC6p"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "WXEUwkx44VjTRlunqyncJOCDeszMoC6p"
-)
-
-func TestFinancialModelingPrep_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/findl/findl.go b/pkg/detectors/findl/findl.go
deleted file mode 100644
index c495fc008cf0..000000000000
--- a/pkg/detectors/findl/findl.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package findl
-
-import (
- "context"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"findl"}) + `\b([a-z0-9]{8}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{12})\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{"findl"}
-}
-
-// FromData will find and optionally verify Findl secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Findl,
- Raw: []byte(resMatch),
- }
-
- if verify {
- timeout := 5 * time.Second
- client.Timeout = timeout
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.findl.com/v1.0/query?limit=6", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-API-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Findl
-}
-
-func (s Scanner) Description() string {
- return "Findl is a service used for searching and querying data. Findl API keys can be used to access and modify this data."
-}
diff --git a/pkg/detectors/findl/findl_integration_test.go b/pkg/detectors/findl/findl_integration_test.go
deleted file mode 100644
index 8566f3b290b8..000000000000
--- a/pkg/detectors/findl/findl_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package findl
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFindl_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FINDL")
- inactiveSecret := testSecrets.MustGetField("FINDL_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a findl secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Findl,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a findl secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Findl,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Findl.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Findl.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/findl/findl_test.go b/pkg/detectors/findl/findl_test.go
deleted file mode 100644
index cb7fc0bd78fd..000000000000
--- a/pkg/detectors/findl/findl_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package findl
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Findl",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "findl_secret": "l06ebuli-0k4m-b5yg-xieh-81s5b9s04ssu"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "l06ebuli-0k4m-b5yg-xieh-81s5b9s04ssu"
-)
-
-func TestFindl_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/finnhub/finnhub.go b/pkg/detectors/finnhub/finnhub.go
deleted file mode 100644
index ea3ab11f9375..000000000000
--- a/pkg/detectors/finnhub/finnhub.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package finnhub
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"finnhub"}) + `\b([0-9a-z]{20})\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{"finnhub"}
-}
-
-// FromData will find and optionally verify Finnhub secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Finnhub,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://finnhub.io/api/v1/calendar/economic?token=%s", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Finnhub
-}
-
-func (s Scanner) Description() string {
- return "Finnhub is a financial data provider offering APIs to access market data. Finnhub API keys can be used to retrieve economic calendars, stock prices, and other financial information."
-}
diff --git a/pkg/detectors/finnhub/finnhub_integration_test.go b/pkg/detectors/finnhub/finnhub_integration_test.go
deleted file mode 100644
index 809b500305fb..000000000000
--- a/pkg/detectors/finnhub/finnhub_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package finnhub
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFinnhub_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FINNHUB")
- inactiveSecret := testSecrets.MustGetField("FINNHUB_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a finnhub secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Finnhub,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a finnhub secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Finnhub,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Finnhub.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Finnhub.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/finnhub/finnhub_test.go b/pkg/detectors/finnhub/finnhub_test.go
deleted file mode 100644
index d76c71028a9f..000000000000
--- a/pkg/detectors/finnhub/finnhub_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package finnhub
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Finnhub",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "finnhub_secret": "5rjqnul3u250d36i73lc"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "5rjqnul3u250d36i73lc"
-)
-
-func TestFinnHub_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/fixerio/fixerio.go b/pkg/detectors/fixerio/fixerio.go
deleted file mode 100644
index c6b4338014bd..000000000000
--- a/pkg/detectors/fixerio/fixerio.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package fixerio
-
-import (
- "context"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"fixer"}) + `\b([A-Za-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"fixer"}
-}
-
-// FromData will find and optionally verify FixerIO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FixerIO,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://data.fixer.io/api/latest?access_key="+resMatch, nil)
- if err != nil {
- continue
- }
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
-
- // if client_id and client_secret is valid -> 403 {"error":"invalid_grant","error_description":"Invalid authorization code"}
- // if invalid -> 401 {"error":"access_denied","error_description":"Unauthorized"}
- // ingenious!
-
- validResponse := strings.Contains(body, `"success": true`) || strings.Contains(body, `"info":"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption."`)
- defer res.Body.Close()
-
- if res.StatusCode >= 200 && res.StatusCode < 300 && validResponse {
- s1.Verified = true
- }
-
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FixerIO
-}
-
-func (s Scanner) Description() string {
- return "Fixer.io is a foreign exchange rates and currency conversion API. Fixer.io API keys can be used to access and retrieve current and historical foreign exchange rates."
-}
diff --git a/pkg/detectors/fixerio/fixerio_integration_test.go b/pkg/detectors/fixerio/fixerio_integration_test.go
deleted file mode 100644
index 585890c06425..000000000000
--- a/pkg/detectors/fixerio/fixerio_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package fixerio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFixerIO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FIXERIO_TOKEN")
- inactiveSecret := testSecrets.MustGetField("FIXERIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fixerio secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FixerIO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fixerio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FixerIO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FixerIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("FixerIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/fixerio/fixerio_test.go b/pkg/detectors/fixerio/fixerio_test.go
deleted file mode 100644
index a528b72792f8..000000000000
--- a/pkg/detectors/fixerio/fixerio_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package fixerio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Fixerio",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "fixerio_secret": "adAM8pezol6tzRrFufnOmUSd4UUO2DoZ"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "adAM8pezol6tzRrFufnOmUSd4UUO2DoZ"
-)
-
-func TestFixerio_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/flatio/flatio.go b/pkg/detectors/flatio/flatio.go
deleted file mode 100644
index 51599f11029e..000000000000
--- a/pkg/detectors/flatio/flatio.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package flatio
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"flat"}) + `\b([0-9a-z]{128})\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{"flat"}
-}
-
-// FromData will find and optionally verify FlatIO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FlatIO,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.flat.io/v2/me", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FlatIO
-}
-
-func (s Scanner) Description() string {
- return "FlatIO is a music notation software. FlatIO keys can be used to access and modify musical scores and related data."
-}
diff --git a/pkg/detectors/flatio/flatio_integration_test.go b/pkg/detectors/flatio/flatio_integration_test.go
deleted file mode 100644
index 16e8ba94038a..000000000000
--- a/pkg/detectors/flatio/flatio_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package flatio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFlatIO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FLATIO")
- inactiveSecret := testSecrets.MustGetField("FLATIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flatio secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FlatIO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flatio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FlatIO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FlatIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("FlatIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/flatio/flatio_test.go b/pkg/detectors/flatio/flatio_test.go
deleted file mode 100644
index 0638f549be41..000000000000
--- a/pkg/detectors/flatio/flatio_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package flatio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Flatio",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "flatio_secret": "n8dihgssrd0h0vv51l29da4wneg6ypo7qegcem2k3jcs9f6ywisvqu8vdimwp0m7pzo6ohnb01d13trnpun3couzbhvtlkbu2fsy8tliiww9ggis53s7xi9mvejj2idy"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "n8dihgssrd0h0vv51l29da4wneg6ypo7qegcem2k3jcs9f6ywisvqu8vdimwp0m7pzo6ohnb01d13trnpun3couzbhvtlkbu2fsy8tliiww9ggis53s7xi9mvejj2idy"
-)
-
-func TestFlatIO_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/fleetbase/fleetbase.go b/pkg/detectors/fleetbase/fleetbase.go
deleted file mode 100644
index fe6405815536..000000000000
--- a/pkg/detectors/fleetbase/fleetbase.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package fleetbase
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(flb_live_[0-9a-zA-Z]{20})\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{"fleetbase"}
-}
-
-// FromData will find and optionally verify Fleetbase secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Fleetbase,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.fleetbase.io/v1/contacts/", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Fleetbase
-}
-
-func (s Scanner) Description() string {
- return "Fleetbase is a platform for building logistics and supply chain applications. Fleetbase API keys can be used to access and manage logistics data and operations."
-}
diff --git a/pkg/detectors/fleetbase/fleetbase_integration_test.go b/pkg/detectors/fleetbase/fleetbase_integration_test.go
deleted file mode 100644
index 763433b2fb33..000000000000
--- a/pkg/detectors/fleetbase/fleetbase_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package fleetbase
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFleetbase_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FLEETBASE")
- inactiveSecret := testSecrets.MustGetField("FLEETBASE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fleetbase secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Fleetbase,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fleetbase secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Fleetbase,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Fleetbase.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Fleetbase.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/fleetbase/fleetbase_test.go b/pkg/detectors/fleetbase/fleetbase_test.go
deleted file mode 100644
index 06af5b782b15..000000000000
--- a/pkg/detectors/fleetbase/fleetbase_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package fleetbase
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Fleetbase",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "secret": "flb_live_ZtWtb6hVkUMVdUDg2lgK"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "flb_live_ZtWtb6hVkUMVdUDg2lgK"
-)
-
-func TestFleetBase_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/flexport/flexport.go b/pkg/detectors/flexport/flexport.go
deleted file mode 100644
index 1d77d9affac1..000000000000
--- a/pkg/detectors/flexport/flexport.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package flexport
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(shltm_[0-9a-zA-Z-_]{40})`)
-)
-
-// 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{"shltm_"}
-}
-
-// FromData will find and optionally verify Flexport secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Flexport,
- Raw: []byte(match),
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/flexport/",
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- // docs: https://docs.logistics-api.flexport.com/2024-04/tag/Webhooks#operation/GetWebhook
- url := "https://logistics-api.flexport.com/logistics/api/2024-04/webhooks"
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
- if err != nil {
- return false, err
- }
- req.Header.Set("Authorization", "Bearer "+token)
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK, http.StatusForbidden:
- // If the endpoint returns useful information, we can return it as a map.
- return true, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Flexport
-}
-
-func (s Scanner) Description() string {
- return "Flexport is a global logistics company that provides shipping, freight forwarding, and supply chain management services."
-}
diff --git a/pkg/detectors/flexport/flexport_test.go b/pkg/detectors/flexport/flexport_test.go
deleted file mode 100644
index 7096f2a33c9d..000000000000
--- a/pkg/detectors/flexport/flexport_test.go
+++ /dev/null
@@ -1,238 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package flexport
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFlexport_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: "flexport_token = 'shltm_ZnpDDh4AEj_n2WHHqjYErtv3ZGS0kH1bWVdl7V9D'",
- want: []string{"shltm_ZnpDDh4AEj_n2WHHqjYErtv3ZGS0kH1bWVdl7V9D"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
-
-func TestFlexport_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FLEXPORT")
- inactiveSecret := testSecrets.MustGetField("FLEXPORT_INACTIVE")
- secretNoPermissions := testSecrets.MustGetField("FLEXPORT_NO_PERMISSIONS")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified - with permissions",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flexport secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Flexport,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, verified - without permissions",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flexport secret %s within", secretNoPermissions)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Flexport,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flexport secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Flexport,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flexport secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Flexport,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flexport secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Flexport,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Flexport.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Flexport.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/flickr/flickr.go b/pkg/detectors/flickr/flickr.go
deleted file mode 100644
index 629da0ea85de..000000000000
--- a/pkg/detectors/flickr/flickr.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package flickr
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"flickr"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"flickr"}
-}
-
-// FromData will find and optionally verify Flickr secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Flickr,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://www.flickr.com/services/rest/?method=flickr.tags.getHotList&api_key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
- if (res.StatusCode >= 200 && res.StatusCode < 300) && strings.Contains(body, "owner=") {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Flickr
-}
-
-func (s Scanner) Description() string {
- return "Flickr is an image and video hosting service. Flickr API keys can be used to access and modify user data and perform various operations within the Flickr ecosystem."
-}
diff --git a/pkg/detectors/flickr/flickr_integration_test.go b/pkg/detectors/flickr/flickr_integration_test.go
deleted file mode 100644
index 3f830f436ce5..000000000000
--- a/pkg/detectors/flickr/flickr_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package flickr
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFlickr_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FLICKR")
- inactiveSecret := testSecrets.MustGetField("FLICKR_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flickr secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Flickr,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flickr secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Flickr,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Flickr.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Flickr.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/flickr/flickr_test.go b/pkg/detectors/flickr/flickr_test.go
deleted file mode 100644
index 785e31fc74f3..000000000000
--- a/pkg/detectors/flickr/flickr_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package flickr
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Flickr",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "flickr_secret": "x0b3lyve4dzszjak9afwb1bp3bz9z4z3"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "x0b3lyve4dzszjak9afwb1bp3bz9z4z3"
-)
-
-func TestFlickr_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/flightapi/flightapi.go b/pkg/detectors/flightapi/flightapi.go
deleted file mode 100644
index f649e63022ec..000000000000
--- a/pkg/detectors/flightapi/flightapi.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package flightapi
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"flightapi"}) + `\b([a-z0-9]{24})\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{"flightapi"}
-}
-
-// FromData will find and optionally verify FlightApi secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FlightApi,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.flightapi.io/iata/%s/london/airport", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FlightApi
-}
-
-func (s Scanner) Description() string {
- return "FlightApi is a service used for accessing flight-related data. FlightApi keys can be used to query flight information and other related services."
-}
diff --git a/pkg/detectors/flightapi/flightapi_integration_test.go b/pkg/detectors/flightapi/flightapi_integration_test.go
deleted file mode 100644
index 1c6be19b1710..000000000000
--- a/pkg/detectors/flightapi/flightapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package flightapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFlightApi_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FLIGHTAPI")
- inactiveSecret := testSecrets.MustGetField("FLIGHTAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flightapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FlightApi,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flightapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FlightApi,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FlightApi.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("FlightApi.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/flightapi/flightapi_test.go b/pkg/detectors/flightapi/flightapi_test.go
deleted file mode 100644
index 014ad0e97836..000000000000
--- a/pkg/detectors/flightapi/flightapi_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package flightapi
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FlightAPI",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "flightapi_secret": "024j4wjk6671d9kvm8a7iouu"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "024j4wjk6671d9kvm8a7iouu"
-)
-
-func TestFlightAPI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/flightlabs/flightlabs.go b/pkg/detectors/flightlabs/flightlabs.go
deleted file mode 100644
index 38cc6171f9ae..000000000000
--- a/pkg/detectors/flightlabs/flightlabs.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package flightlabs
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(`\b(eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9\.ey[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]{86})\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{"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9"}
-}
-
-func (s Scanner) getClient() *http.Client {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- return client
-}
-
-// FromData will find and optionally verify FlightLabs secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueKeys := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[match[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FlightLabs,
- Raw: []byte(key),
- }
-
- if verify {
- isVerified, verificationErr := verifyMatch(ctx, s.getClient(), key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, key)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, secret string) (bool, error) {
- // API Reference: https://www.goflightlabs.com/airports-by-filters
-
- url := fmt.Sprintf("https://www.goflightlabs.com/airports-by-filter?access_key=%s&iata_code=JFK", secret)
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
- if err != nil {
- return false, err
- }
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FlightLabs
-}
-
-func (s Scanner) Description() string {
- return "FlightLabs provides a comprehensive API for accessing real-time and historical flight data. The API keys can be used to query flight information, schedules, and other related data."
-}
diff --git a/pkg/detectors/flightlabs/flightlabs_integration_test.go b/pkg/detectors/flightlabs/flightlabs_integration_test.go
deleted file mode 100644
index aeb228e9aaa6..000000000000
--- a/pkg/detectors/flightlabs/flightlabs_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package flightlabs
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFlightLabs_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FLIGHTLABS")
- inactiveSecret := testSecrets.MustGetField("FLIGHTLABS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flightlabs secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FlightLabs,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flightlabs secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FlightLabs,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FlightLabs.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("FlightLabs.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/flightlabs/flightlabs_test.go b/pkg/detectors/flightlabs/flightlabs_test.go
deleted file mode 100644
index 47d3d9213325..000000000000
--- a/pkg/detectors/flightlabs/flightlabs_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package flightlabs
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FlightLabs",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "flightlabs_secret": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.ey3UspgSVM9j311NMff2N27tGEWSmr8sl1SguOxwzelJSYPOOVp-8BwHsdHqWKWpoVvZAc4kXKJ2kpROZ1RY_0xSj51iWOoi5UvvxOlaIHTzMEEiudOJRQuzYxwtqtl1rZyRlFuxTm0YR5wWPFM0GlWzmCf_yKz.atNcL556uLcZ9D6MTIlQoC9hD1u3EbBqL6nb32cgFowGosYnqkSgbCFPLg6LIhK_PADfDzUY2bTEsk7uEIbGxP"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.ey3UspgSVM9j311NMff2N27tGEWSmr8sl1SguOxwzelJSYPOOVp-8BwHsdHqWKWpoVvZAc4kXKJ2kpROZ1RY_0xSj51iWOoi5UvvxOlaIHTzMEEiudOJRQuzYxwtqtl1rZyRlFuxTm0YR5wWPFM0GlWzmCf_yKz.atNcL556uLcZ9D6MTIlQoC9hD1u3EbBqL6nb32cgFowGosYnqkSgbCFPLg6LIhK_PADfDzUY2bTEsk7uEIbGxP"
-)
-
-func TestFlightLabs_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/flightstats/flightstats.go b/pkg/detectors/flightstats/flightstats.go
deleted file mode 100644
index 6517098b2dcc..000000000000
--- a/pkg/detectors/flightstats/flightstats.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package flightstats
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"flightstats"}) + `\b([0-9a-z]{8})\b`)
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"flightstats"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"flightstats"}
-}
-
-// FromData will find and optionally verify Flightstats secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
- resId := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Flightstats,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.flightstats.com/flex/aircraft/rest/v1/json/availableFields?appId=%s&appKey=%s", resId, resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
- validResponse := (res.StatusCode >= 200 && res.StatusCode < 300 && strings.Contains(body, "id")) || (res.StatusCode == 403 && strings.Contains(body, "application is not active"))
- if validResponse {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Flightstats
-}
-
-func (s Scanner) Description() string {
- return "Flightstats provides APIs for accessing flight data and statistics. Flightstats API keys can be used to retrieve and manipulate flight-related information."
-}
diff --git a/pkg/detectors/flightstats/flightstats_integration_test.go b/pkg/detectors/flightstats/flightstats_integration_test.go
deleted file mode 100644
index 018ae17b83eb..000000000000
--- a/pkg/detectors/flightstats/flightstats_integration_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package flightstats
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFlightstats_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- id := testSecrets.MustGetField("FLIGHTSTATS_ID")
- secret := testSecrets.MustGetField("FLIGHTSTATS_KEY")
- inactiveSecret := testSecrets.MustGetField("FLIGHTSTATS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flightstats secret %s within flightstats id %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Flightstats,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flightstats secret %s within flightstats id %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Flightstats,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Flightstats.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Flightstats.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/flightstats/flightstats_test.go b/pkg/detectors/flightstats/flightstats_test.go
deleted file mode 100644
index c1c3b9ea008f..000000000000
--- a/pkg/detectors/flightstats/flightstats_test.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package flightstats
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FlightStats",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "flightstats_id":"35o5omng",
- "flightstats_secret": "ksqxv0hkdkli9s71bd7ebfl5cijbab7f"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "ksqxv0hkdkli9s71bd7ebfl5cijbab7f"
-)
-
-func TestFlightStats_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/float/float.go b/pkg/detectors/float/float.go
deleted file mode 100644
index 5ce95000a5fe..000000000000
--- a/pkg/detectors/float/float.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package float
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"float"}) + `\b([a-f0-9]{16}[A-Za-z0-9+/]{42,43}=)`)
-)
-
-// 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{"float"}
-}
-
-// FromData will find and optionally verify Float secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Float,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.float.com/v3/people", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- req.Header.Add("User-Agent", "TruffleHog3 (example@example.com)")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Float
-}
-
-func (s Scanner) Description() string {
- return "Float is a resource management software used for planning and scheduling projects. Float API keys can be used to access and modify project data and schedules."
-}
diff --git a/pkg/detectors/float/float_integration_test.go b/pkg/detectors/float/float_integration_test.go
deleted file mode 100644
index e4b3acb0b73d..000000000000
--- a/pkg/detectors/float/float_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package float
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFloat_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FLOAT_TOKEN")
- inactiveSecret := testSecrets.MustGetField("FLOAT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a float secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Float,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a float secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Float,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Float.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Float.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/float/float_test.go b/pkg/detectors/float/float_test.go
deleted file mode 100644
index 50d5af958235..000000000000
--- a/pkg/detectors/float/float_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package float
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Float",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "float_secret": "50604f993cb9e4dfCsmIjdN5bCx5FnnfaukUdv7S9sm9L5wB2fZSUkZqHn="
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "50604f993cb9e4dfCsmIjdN5bCx5FnnfaukUdv7S9sm9L5wB2fZSUkZqHn="
-)
-
-func TestFloat_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/flowflu/flowflu.go b/pkg/detectors/flowflu/flowflu.go
deleted file mode 100644
index 5bdb9453ed07..000000000000
--- a/pkg/detectors/flowflu/flowflu.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package flowflu
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"flowflu"}) + `\b([a-zA-Z0-9]{51})\b`)
- accountPat = regexp.MustCompile(detectors.PrefixRegex([]string{"flowflu", "account"}) + `\b([a-zA-Z0-9]{4,30})\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{"flowflu"}
-}
-
-// FromData will find and optionally verify FlowFlu secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- accountMatches := accountPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, accountMatch := range accountMatches {
-
- resAccount := strings.TrimSpace(accountMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FlowFlu,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s.flowlu.com/api/v1/module/crm/lead/list?api_key=%s", resAccount, resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
-
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `total_result`)
-
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FlowFlu
-}
-
-func (s Scanner) Description() string {
- return "FlowFlu is a service used for managing customer relationships and projects. FlowFlu API keys can be used to access and manipulate CRM data."
-}
diff --git a/pkg/detectors/flowflu/flowflu_integration_test.go b/pkg/detectors/flowflu/flowflu_integration_test.go
deleted file mode 100644
index f5098c0b4744..000000000000
--- a/pkg/detectors/flowflu/flowflu_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package flowflu
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFlowFlu_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- account := testSecrets.MustGetField("FLOWFLU_ACCOUNT")
- secret := testSecrets.MustGetField("FLOWFLU_TOKEN")
- inactiveSecret := testSecrets.MustGetField("FLOWFLU_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flowflu secret %s within flowflu account %s", secret, account)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FlowFlu,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flowflu secret %s within flowflu account %s but not valid", inactiveSecret, account)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FlowFlu,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FlowFlu.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("FlowFlu.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/flowflu/flowflu_test.go b/pkg/detectors/flowflu/flowflu_test.go
deleted file mode 100644
index 0778940cce39..000000000000
--- a/pkg/detectors/flowflu/flowflu_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package flowflu
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FlowFlu",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "flowflu_account": "tsPX0KAuOZPMy9BMjTwmph",
- "flowflu_secret": "QdUZ0jRet5Z8nQjMgbLUGHZqShpFHCydCnL7hpTNXnwpUy75SJi"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secrets = []string{
- "QdUZ0jRet5Z8nQjMgbLUGHZqShpFHCydCnL7hpTNXnwpUy75SJi",
- "QdUZ0jRet5Z8nQjMgbLUGHZqShpFHCydCnL7hpTNXnwpUy75SJi",
- }
-)
-
-func TestFlowFlu_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/flutterwave/flutterwave.go b/pkg/detectors/flutterwave/flutterwave.go
deleted file mode 100644
index 3de6afc2bc3a..000000000000
--- a/pkg/detectors/flutterwave/flutterwave.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package flutterwave
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
-
- keyPat = regexp.MustCompile(`\b(FLWSECK-[0-9a-z]{32}-X)\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{"FLWSECK-"}
-}
-
-// FromData will find and optionally verify Flutterwave secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Flutterwave,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.flutterwave.com/v3/subaccounts", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Flutterwave
-}
-
-func (s Scanner) Description() string {
- return "Flutterwave is a payment technology company providing seamless and secure payment solutions for businesses. Flutterwave API keys can be used to access and manage payment services and transactions."
-}
diff --git a/pkg/detectors/flutterwave/flutterwave_integration_test.go b/pkg/detectors/flutterwave/flutterwave_integration_test.go
deleted file mode 100644
index e558209ed380..000000000000
--- a/pkg/detectors/flutterwave/flutterwave_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package flutterwave
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFlutterwave_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FLUTTERWAVE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("FLUTTERWAVE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flutterwave secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Flutterwave,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flutterwave secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Flutterwave,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Flutterwave.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Flutterwave.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/flutterwave/flutterwave_test.go b/pkg/detectors/flutterwave/flutterwave_test.go
deleted file mode 100644
index 7937ff969efb..000000000000
--- a/pkg/detectors/flutterwave/flutterwave_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package flutterwave
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FlutterWave",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "flutterwave_secret": "FLWSECK-aylhdv2oo3wf5tylj8s4d9bqb8adoebx-X"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "FLWSECK-aylhdv2oo3wf5tylj8s4d9bqb8adoebx-X"
-)
-
-func TestFlutterWave_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/flyio/flyio.go b/pkg/detectors/flyio/flyio.go
deleted file mode 100644
index 716513ae0b50..000000000000
--- a/pkg/detectors/flyio/flyio.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package flyio
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.CustomFalsePositiveChecker = (*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(FlyV1 fm\d+_[A-Za-z0-9+\/=,_-]{500,700})\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{"FlyV1"}
-}
-
-// FromData will find and optionally verify Flyio secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FlyIO,
- Raw: []byte(match),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyMatch(ctx, client, match)
-
- s1.Verified = isVerified
- if verificationErr != nil {
- s1.SetVerificationError(verificationErr, match)
- }
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- // Not setting org_slug intentionally, as it's not required for the token to be valid.
- // Initially, an organization named "personal" is created by FlyIO when the user signs up for an account. We cannot rely on this as it can be deleted.
- // 403 is returned if incorrect org_slug is sent.
- // 401 is returned if the token is invalid.
- // 400 is returned if the token is valid but no org_slug is sent.
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.machines.dev/v1/apps?org_slug=", http.NoBody)
- if err != nil {
- return false, nil
- }
- req.Header.Add("accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusBadRequest:
- // Not setting org_slug returns a 400 error, which is expected.
- return true, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil
- default:
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- return false, err
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FlyIO
-}
-
-func (s Scanner) Description() string {
- return "Fly.io is a platform for running applications globally. Fly.io tokens can be used to access the Fly.io API and manage applications."
-}
-
-func (s Scanner) IsFalsePositive(result detectors.Result) (bool, string) {
- // ignore AAAAAA for Flyio detector
- if strings.Contains(string(result.Raw), "AAAAAA") {
- return false, ""
- }
-
- // For non-matching patterns, fall back to default false positive logic
- return detectors.IsKnownFalsePositive(string(result.Raw), detectors.DefaultFalsePositives, true)
-}
diff --git a/pkg/detectors/flyio/flyio_integration_test.go b/pkg/detectors/flyio/flyio_integration_test.go
deleted file mode 100644
index cd67bd2162ce..000000000000
--- a/pkg/detectors/flyio/flyio_integration_test.go
+++ /dev/null
@@ -1,148 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package flyio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFlyio_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FLYIO")
- inactiveSecret := testSecrets.MustGetField("FLYIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flyio secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FlyIO,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flyio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FlyIO,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flyio secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FlyIO,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a flyio secret %s within", secret)),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_FlyIO,
- Verified: false,
- }
- r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 404"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Flyio.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo")
- ignoreUnexported := cmpopts.IgnoreUnexported(detectors.Result{})
- if diff := cmp.Diff(got, tt.want, ignoreOpts, ignoreUnexported); diff != "" {
- t.Errorf("Flyio.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/flyio/flyio_test.go b/pkg/detectors/flyio/flyio_test.go
deleted file mode 100644
index a8b9631f8b71..000000000000
--- a/pkg/detectors/flyio/flyio_test.go
+++ /dev/null
@@ -1,165 +0,0 @@
-package flyio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFlyio_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: "flyio_token = 'FlyV1 fm2_AD1shwGbLSpZSPEXM1vhcbPZowurCDkXySOOJj0w4G2abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'",
- want: []string{"FlyV1 fm2_AD1shwGbLSpZSPEXM1vhcbPZowurCDkXySOOJj0w4G2abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"},
- },
- {
- name: "invalid pattern - too short",
- input: "flyio_token = 'FlyV1 fm2_short'",
- want: []string{},
- },
- {
- name: "invalid pattern - wrong prefix",
- input: "flyio_token = 'FlyV2 fm2_AD1shwGbLSpZSPEXM1vhcbPZowurCDkXySOOJj0w4G2abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'",
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(test.want) > 0 && len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 && len(test.want) > 0 {
- t.Errorf("did not receive result")
- } else if len(results) > 0 && len(test.want) == 0 {
- t.Errorf("expected no results, but received %d", len(results))
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
-
-func TestFlyio_IsFalsePositive(t *testing.T) {
- s := Scanner{}
-
- tests := []struct {
- name string
- token string
- expected bool
- reason string
- }{
- {
- name: "token with AAAAAA - should not be flagged as false positive",
- token: "FlyV1 fm2_abcdAAAAAA1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
- expected: false,
- reason: "",
- },
- {
- name: "token with example pattern - should be false positive",
- token: "FlyV1 fm2_1234example567890zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321",
- expected: true,
- reason: "contains term: example",
- },
- {
- name: "token with sample pattern - should be false positive",
- token: "FlyV1 fm2_1234sample567890zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321",
- expected: true,
- reason: "contains term: sample",
- },
- {
- name: "token with xxxxxx pattern - should be false positive",
- token: "FlyV1 fm2_1234xxxxxx567890zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321",
- expected: true,
- reason: "contains term: xxxxxx",
- },
- {
- name: "valid token without AAAAAA - should not be false positive",
- token: "FlyV1 fm2_1234567890zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321",
- expected: false,
- reason: "",
- },
- {
- name: "regular string without pattern - should not be false positive",
- token: "XYZABC123789def456",
- expected: false,
- reason: "",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_FlyIO,
- Raw: []byte(tt.token),
- }
-
- isFP, reason := s.IsFalsePositive(result)
-
- if isFP != tt.expected {
- t.Errorf("IsFalsePositive() got = %v, want %v (reason: %s)", isFP, tt.expected, reason)
- }
-
- if tt.expected && reason != tt.reason {
- t.Errorf("IsFalsePositive() reason got = %v, want %v", reason, tt.reason)
- }
- })
- }
-}
-
-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)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/fmfw/fmfw.go b/pkg/detectors/fmfw/fmfw.go
deleted file mode 100644
index 70438465f88c..000000000000
--- a/pkg/detectors/fmfw/fmfw.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package fmfw
-
-import (
- "context"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"fmfw"}) + `\b([a-zA-Z0-9_-]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"fmfw"}) + `\b([a-zA-Z0-9-]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"fmfw"}
-}
-
-// FromData will find and optionally verify Fmfw secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- tokenPatMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- userPatMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Fmfw,
- Raw: []byte(tokenPatMatch),
- }
-
- if verify {
- timeout := 10 * time.Second
- client.Timeout = timeout
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.fmfw.io/api/3/spot/balance", nil)
- if err != nil {
- continue
- }
- req.SetBasicAuth(userPatMatch, tokenPatMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Fmfw
-}
-
-func (s Scanner) Description() string {
- return "FMFW is a cryptocurrency exchange platform. FMFW API keys can be used to access and manage account data and perform trading operations."
-}
diff --git a/pkg/detectors/fmfw/fmfw_integration_test.go b/pkg/detectors/fmfw/fmfw_integration_test.go
deleted file mode 100644
index 2aa351fc375a..000000000000
--- a/pkg/detectors/fmfw/fmfw_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package fmfw
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFmfw_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FMFW")
- user := testSecrets.MustGetField("FMFW_USER")
- inactiveSecret := testSecrets.MustGetField("FMFW_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fmfw secret %s within fmfw %s", secret, user)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Fmfw,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a fmfw secret %s within fmfw %s but not valid", inactiveSecret, user)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Fmfw,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Fmfw.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Fmfw.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/fmfw/fmfw_test.go b/pkg/detectors/fmfw/fmfw_test.go
deleted file mode 100644
index 34725611d66d..000000000000
--- a/pkg/detectors/fmfw/fmfw_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package fmfw
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FMFW",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "fmfw_key": "cno3jaTTtgBeo3b_y82FPdrw4Yxfspvd",
- "fmfw_id": "nsrD8XVjeXc4Z-uGw6CgTBRXHmTjbizL"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secrets = []string{
- "cno3jaTTtgBeo3b_y82FPdrw4Yxfspvd",
- "nsrD8XVjeXc4Z-uGw6CgTBRXHmTjbizL",
- }
-)
-
-func TestFmFw_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/formbucket/formbucket.go b/pkg/detectors/formbucket/formbucket.go
deleted file mode 100644
index 4377a32bb68f..000000000000
--- a/pkg/detectors/formbucket/formbucket.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package formbucket
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"formbucket"}) + `\b([0-9A-Za-z]{1,}.[0-9A-Za-z]{1,}\.[0-9A-Z-a-z\-_]{1,})`)
-)
-
-// 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{"formbucket"}
-}
-
-// FromData will find and optionally verify FormBucket secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FormBucket,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.formbucket.com/v1/profile", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- body, errBody := io.ReadAll(res.Body)
- if errBody != nil {
- continue
- }
- bodyString := string(body)
- validResponse := strings.Contains(bodyString, `created_on`)
- defer res.Body.Close()
- if errBody == nil {
- if res.StatusCode >= 200 && res.StatusCode < 300 && validResponse {
- s1.Verified = true
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Description() string {
- return "FormBucket is a service used to collect and manage form submissions. The detected credential can be used to access and modify form data."
-}
-
-type Response struct {
- Anonymous bool `json:"anonymous"`
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FormBucket
-}
diff --git a/pkg/detectors/formbucket/formbucket_integration_test.go b/pkg/detectors/formbucket/formbucket_integration_test.go
deleted file mode 100644
index ea3a25404b60..000000000000
--- a/pkg/detectors/formbucket/formbucket_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package formbucket
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFormBucket_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FORMBUCKET")
- inactiveSecret := testSecrets.MustGetField("FORMBUCKET_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a formbucket secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FormBucket,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a formbucket secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FormBucket,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FormBucket.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("FormBucket.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/formbucket/formbucket_test.go b/pkg/detectors/formbucket/formbucket_test.go
deleted file mode 100644
index e50e295ab38d..000000000000
--- a/pkg/detectors/formbucket/formbucket_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package formbucket
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FormBucket",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "formbucket_secret": "qE4P6YytmrnbI4o7xmr3Ct9umMOgM7CKqlUTvdMsXICpUEEow2ZDQi0CyZ7AYir4BkqsxvKdV33095olnQO6gkHgoZsSHPG41oqLrrM3g.l1Vt_Jv9iuT7w4si"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "qE4P6YytmrnbI4o7xmr3Ct9umMOgM7CKqlUTvdMsXICpUEEow2ZDQi0CyZ7AYir4BkqsxvKdV33095olnQO6gkHgoZsSHPG41oqLrrM3g.l1Vt_Jv9iuT7w4si"
-)
-
-func TestFormBucket_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/formcraft/formcraft.go b/pkg/detectors/formcraft/formcraft.go
deleted file mode 100644
index 7d3402843371..000000000000
--- a/pkg/detectors/formcraft/formcraft.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package formcraft
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"formcraft"}) + `\b([0-9a-z]{16})\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{"formcraft"}
-}
-
-// FromData will find and optionally verify Formcraft secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Formcraft,
- Raw: []byte(resMatch),
- }
-
- if verify {
- data := fmt.Sprintf("%s:", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://formcrafts.com/api/v1/", nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Formcraft
-}
-
-func (s Scanner) Description() string {
- return "Formcraft is a form building and data collection service. Formcraft keys can be used to access and manage forms and collected data."
-}
diff --git a/pkg/detectors/formcraft/formcraft_integration_test.go b/pkg/detectors/formcraft/formcraft_integration_test.go
deleted file mode 100644
index d8ef58a46fe3..000000000000
--- a/pkg/detectors/formcraft/formcraft_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package formcraft
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFormcraft_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FORMCRAFT")
- inactiveSecret := testSecrets.MustGetField("FORMCRAFT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a formcraft secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Formcraft,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a formcraft secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Formcraft,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Formcraft.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Formcraft.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/formcraft/formcraft_test.go b/pkg/detectors/formcraft/formcraft_test.go
deleted file mode 100644
index a6411049a3cf..000000000000
--- a/pkg/detectors/formcraft/formcraft_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package formcraft
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FormCraft",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "formcraft_secret": "zgej8qae3ehc0mjo"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "zgej8qae3ehc0mjo"
-)
-
-func TestFormCraft_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/formio/formio.go b/pkg/detectors/formio/formio.go
deleted file mode 100644
index 52b3f3670c41..000000000000
--- a/pkg/detectors/formio/formio.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package formio
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"formio"}) + `\b(eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[0-9A-Za-z]{220,310}\.[0-9A-Z-a-z\-_]{43}[ \r\n]{1})`)
-)
-
-// 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{"formio"}
-}
-
-// FromData will find and optionally verify FormIO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FormIO,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://formio.form.io/current", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("x-jwt-token", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FormIO
-}
-
-func (s Scanner) Description() string {
- return "FormIO is a platform for building form-based applications. FormIO JWT tokens can be used to authenticate and interact with FormIO services."
-}
diff --git a/pkg/detectors/formio/formio_integration_test.go b/pkg/detectors/formio/formio_integration_test.go
deleted file mode 100644
index d9c2ec280f22..000000000000
--- a/pkg/detectors/formio/formio_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package formio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFormIO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FORMIO")
- inactiveSecret := testSecrets.MustGetField("FORMIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a formio secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FormIO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a formio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FormIO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("FormIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("FormIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/formio/formio_test.go b/pkg/detectors/formio/formio_test.go
deleted file mode 100644
index b12a5e71a17a..000000000000
--- a/pkg/detectors/formio/formio_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package formio
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FormIO",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "formio_secret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.3IJk8Ys67c6tWlZi346ptymjjgkzwSyE5G2RbPS3kNyxuD4DFUj1vJFqlzZUTwUTHzhTEiUCPG3xtBFPfEBCGBtKDdh4SB3QhWHZAvEx3v61Mv1bsg3dhiKeGEJBluxNr8FRWHNmCaWq7KQpqK6YDX7ItacPKYKzOWXw16Swwj8lnKORhut3TjIsNa0dSoTCGeVZQey0RD0GuWuuXIz5Bu6xQoVnexXGKmbm3wu4VMxsXaquKvW6xXo.lQWeje6Ck-SNJR1LEwHqOFjVfad7-SXyV2nivyHnpxt "
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.3IJk8Ys67c6tWlZi346ptymjjgkzwSyE5G2RbPS3kNyxuD4DFUj1vJFqlzZUTwUTHzhTEiUCPG3xtBFPfEBCGBtKDdh4SB3QhWHZAvEx3v61Mv1bsg3dhiKeGEJBluxNr8FRWHNmCaWq7KQpqK6YDX7ItacPKYKzOWXw16Swwj8lnKORhut3TjIsNa0dSoTCGeVZQey0RD0GuWuuXIz5Bu6xQoVnexXGKmbm3wu4VMxsXaquKvW6xXo.lQWeje6Ck-SNJR1LEwHqOFjVfad7-SXyV2nivyHnpxt"
-)
-
-func TestFormIO_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/formsite/formsite.go b/pkg/detectors/formsite/formsite.go
deleted file mode 100644
index 59344861b47d..000000000000
--- a/pkg/detectors/formsite/formsite.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package formsite
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"formsite"}) + `\b([a-zA-Z0-9]{32})\b`)
- serverPat = regexp.MustCompile(detectors.PrefixRegex([]string{"formsite"}) + `\b(fs[0-9]{1,4})\b`)
- userPat = regexp.MustCompile(detectors.PrefixRegex([]string{"formsite"}) + `\b([a-zA-Z0-9]{6})\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{"formsite"}
-}
-
-// FromData will find and optionally verify Formsite secrets in a given set of bytes..
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- serverMatches := serverPat.FindAllStringSubmatch(dataStr, -1)
- userMatches := userPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, serverMatch := range serverMatches {
- resServerMatch := strings.TrimSpace(serverMatch[1])
- for _, userMatch := range userMatches {
- resUserMatch := strings.TrimSpace(userMatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Formsite,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s.formsite.com/api/v2/%s/forms", resServerMatch, resUserMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Formsite
-}
-
-func (s Scanner) Description() string {
- return "Formsite is an online form builder service. Formsite API keys can be used to access and manage forms and data submissions."
-}
diff --git a/pkg/detectors/formsite/formsite_integration_test.go b/pkg/detectors/formsite/formsite_integration_test.go
deleted file mode 100644
index da9944d08134..000000000000
--- a/pkg/detectors/formsite/formsite_integration_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package formsite
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFormsite_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("FORMSITE")
- inactiveSecret := testSecrets.MustGetField("FORMSITE_INACTIVE")
- server := testSecrets.MustGetField("FORMSITE_SERVER")
- user := testSecrets.MustGetField("FORMSITE_USER")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a formsite secret %s within formsite server %s formsite user %s", secret, server, user)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Formsite,
- Verified: true,
- },
- {
- DetectorType: detectorspb.DetectorType_Formsite,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a formsite secret %s within but not valid formsite server %s formsite user %s", inactiveSecret, server, user)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Formsite,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Formsite,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Formsite.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Formsite.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/formsite/formsite_test.go b/pkg/detectors/formsite/formsite_test.go
deleted file mode 100644
index c323818a024c..000000000000
--- a/pkg/detectors/formsite/formsite_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package formsite
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "Formsite",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "formsite_server": "fs02",
- "formsite_user": "ITest2",
- "formsite_secret": "8PKXsB1ohFUGnw0j8y3g9pRUvDj0I1Ha"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secret = "8PKXsB1ohFUGnw0j8y3g9pRUvDj0I1Ha"
-)
-
-func TestFormsite_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{secret},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/foursquare/foursquare.go b/pkg/detectors/foursquare/foursquare.go
deleted file mode 100644
index b34a53de18f6..000000000000
--- a/pkg/detectors/foursquare/foursquare.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package foursquare
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"foursquare"}) + `\b([0-9A-Z]{48})\b`)
- secretMatch = regexp.MustCompile(detectors.PrefixRegex([]string{"foursquare"}) + `\b([0-9A-Z]{48})\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{"foursquare"}
-}
-
-// FromData will find and optionally verify Foursquare secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretMatch.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, secretMatch := range secretMatches {
- resSecret := strings.TrimSpace(secretMatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_FourSquare,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resSecret),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.foursquare.com/v2/venues/trending?client_id=%s&client_secret=%s&v=20211019&near=LA", resMatch, resSecret), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_FourSquare
-}
-
-func (s Scanner) Description() string {
- return "Foursquare is a technology company that uses location intelligence to build meaningful consumer experiences and business solutions. Foursquare API keys can be used to access and interact with their services."
-}
diff --git a/pkg/detectors/foursquare/foursquare_integration_test.go b/pkg/detectors/foursquare/foursquare_integration_test.go
deleted file mode 100644
index e6b1076963d8..000000000000
--- a/pkg/detectors/foursquare/foursquare_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package foursquare
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestFoursquare_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- id := testSecrets.MustGetField("FOURSQUARE")
- secret := testSecrets.MustGetField("FOURSQUARE_SECRET")
- inactiveId := testSecrets.MustGetField("FOURSQUARE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a foursquare secret %s within foursquare id %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FourSquare,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a foursquare secret %s within foursquare id %s but not valid", secret, inactiveId)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_FourSquare,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Foursquare.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Foursquare.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/foursquare/foursquare_test.go b/pkg/detectors/foursquare/foursquare_test.go
deleted file mode 100644
index 05a1e65e3a03..000000000000
--- a/pkg/detectors/foursquare/foursquare_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package foursquare
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `[{
- "_id": "1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5",
- "name": "FormSquare",
- "type": "Detector",
- "api": true,
- "authentication_type": "",
- "verification_url": "https://api.example.com/example",
- "test_secrets": {
- "foursquare_key": "NUAL8SFC7DGMAA0V79J57TSMOVZTH5HI5B7IM1BCF4L3IQXG",
- "foursquare_secret": "CII2183RFI60TZGXHK25A06VWZYE19IBMGJQZAIG3PPJFZ6M"
- },
- "expected_response": "200",
- "method": "GET",
- "deprecated": false
- }]`
- secrets = []string{
- "CII2183RFI60TZGXHK25A06VWZYE19IBMGJQZAIG3PPJFZ6MCII2183RFI60TZGXHK25A06VWZYE19IBMGJQZAIG3PPJFZ6M",
- "NUAL8SFC7DGMAA0V79J57TSMOVZTH5HI5B7IM1BCF4L3IQXGCII2183RFI60TZGXHK25A06VWZYE19IBMGJQZAIG3PPJFZ6M",
- "CII2183RFI60TZGXHK25A06VWZYE19IBMGJQZAIG3PPJFZ6MNUAL8SFC7DGMAA0V79J57TSMOVZTH5HI5B7IM1BCF4L3IQXG",
- "NUAL8SFC7DGMAA0V79J57TSMOVZTH5HI5B7IM1BCF4L3IQXGNUAL8SFC7DGMAA0V79J57TSMOVZTH5HI5B7IM1BCF4L3IQXG",
- }
-)
-
-func TestFourSquare_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: secrets,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/fp_badlist.txt b/pkg/detectors/fp_badlist.txt
deleted file mode 100644
index 957f31263028..000000000000
--- a/pkg/detectors/fp_badlist.txt
+++ /dev/null
@@ -1,288 +0,0 @@
-value
-from
-array
-uint
-boolean
-config
-parse
-func
-param
-cancel
-export
-substr
-name
-utils
-token
-data
-encode
-else
-auth
-define
-space
-ident
-block
-type
-index
-case
-safe
-decrypt
-event
-message
-args
-head
-cookie
-buffer
-return
-throw
-derive
-bits
-bytes
-node
-code
-const
-logger
-hash
-source
-tuple
-this
-style
-version
-batch
-enable
-disable
-remove
-port
-modifi
-kind
-sort
-format
-text
-numer
-punct
-litera
-context
-layer
-delta
-init
-final
-color
-cancel
-calc
-append
-slice
-force
-escape
-exit
-frame
-char
-align
-implem
-keyword
-trace
-truncate
-group
-href
-scale
-model
-visual
-model
-never
-win32
-goto
-small
-large
-lexer
-replace
-variab
-close
-defer
-start
-;var
-storage
-blob
-cred
-math
-.xml
-conflict
-hack
-package
-contract
-schema
-vec<
-ed25519
-prefix
-suffix
-compress
-hmac
-sha256
-request
-base
-rest
-session
-spec
-date
-time
-cache
-build
-check
-install
-asset
-vendor
-x509
-usage
-errno
-crypto
-sha384
-sha512
-sha1
-ecdsa
-algo
-cert
-progress
-marshal
-strconv
-primary
-unseal
-pkcs
-recurs
-struct
-entry
-vault:v
-lease
-share
-mouse
-press
-public
-cloud
-amq.gem
-resp
-ring
-error
-revoke
-encrypt
-binary
-2018-
-2019-
-2020-
-2021-
-2022-
-byte
-root
-readon
-test
-2048
-match
-private
-key_
-aes256
-aes128
-state
-alloc
-proto
-term
-server
-step
-limit
-backend
-len(
-increm
-bucket
-object
-last
-first
-start
-stop
-seal
-transit
-offset
-pointer
-arr[
-cluster
-value
-read
-sign
-leave
-lock
-part
-central
-local
-http:
-https:
-delete
-insert
-append
-table
-mutat
-colon
-bound
-{key
-valid
-proc
-enum
-query
-open
-module
-program
-import
-pinned
-pubexp
-keygen
-shim
-expr
-buf[
-keyid
-.key
-keys
-queue
-sha-1
-sha-256
-sha-512
-sha-384
-user
-info
-[idx
-gray
-black
-white
-yellow
-orange
-purple
-=val
-key=
-policy
-field
-json
-piece
-depth
-label
-daemon
-cron
-uuid
-k8s.
-role
-application
-explic
-random
-DES3
-3DES
-amq.gen
-xml:"
-tag:
-.Get
-.Put
-.Delete
-extend
-split
-option
-fontsize
-"
-keyboard
-custom
-item
-emulate
-iphone
-develop
-master
-slave
-secondary
-example
diff --git a/pkg/detectors/fp_programmingbooks.txt b/pkg/detectors/fp_programmingbooks.txt
deleted file mode 100644
index 8b6c1a287534..000000000000
--- a/pkg/detectors/fp_programmingbooks.txt
+++ /dev/null
@@ -1,2871 +0,0 @@
---+--+--
--->
-$${balance
-$curry(...args
-${email
-$.getjson(url
-$ident
-$('
-add(multiply(x
-addnextgrade
-add-one
-add_one
-addpage
-address
-add
-add(self
-add
-addstudents
-add/target
-addten
-add_text
-add-two
-adequate
-adhere
-adjust
-administrative
-admirable
-admonition
-advanced
-advent
-advertising
-advice
-advisable
-aesthetic
-affect
-aforementioned
-afraid
-a.get('team
-aggregates
-aimlessly
-a.iter
-ajaxcall
-ajax(url,cb
-alarms!
-albeit
-albert
-algebra
-algorithm
-all.empty
-all(false
-allocate
-all-or-nothing
-allthechildren
-all(true
-allupper
-almost
-alphabetic
-already
-alt="a
-although
-altogether
-_always_
-always
-amalgamations
-a'].map($
-amateur
-amazement
-amazing
-ambiguity
-ambition
-amd-style
-amenable
-amending
-amidst
-amoduleineed
-amount
-ampersand
-analog
-analysis
-anarchy
-anatomy
-@anaufalm
-ancestor
-anchor
-ancients
-and/or
-andrew
-andthisonetoo
-angels
-animal
-aniston
-annihilate
-annotate
-announcement
-annoyed
-
-anonymous
-another
-answer
-anti-class
-anxious
-any.empty
-any(false
-anyfunctor
-anyhow
-anymore
-anyone
-anything
-any(true
-anyway
-anywhere
-a.of(f
-apiendpoints
-api.flickr.com
-ap`ing
-apostrophe
-ap(other
-apparent
-app('cats
-appeal
-append
-appetizer
-apple”
-applicability
-appreciate
-approach
-app
-arabic
-arbitrarily
-arcane
-architectural
-arc>
-arc
-area(&self
-aren't
-aren’t
-args.length
-argstr
-arguably
-arguing
-argument
-arithmetic
-armstrong
-around
-arrangement
-arrcopy
-arr.entries
-arrived
-arr.length
-art4thesould
-arthur
-article
-artifact
-artist
-as_bytes
-ashamed
-asking
-askquestion
-asm.js
-as_mut_ptr
-a`
-aspect
-as_ref
-assemble
-assert!
-assign
-assist
-associate
-‘associated
-assortment
-assume
-assure
-asterisk
-ast.ident
-astound
-ast)—to
-atomic
-attach
-attempt
-attend
-attitude
-attractions
-attribute
-audience
-audited
-augments
-august
-austin
-authenticate
-author
-auto-complete
-autocompletion
-automate
-autosave
-available
-a_value
-average
-awesome!
-awhile
-awkward
-azure
-b${str
-babylonians
-baby_name
-backed
-background
-backing
-back_of_house
-backported
-backspace
-back-tick
-backtrace
-backward
-badidea
-bahasa
-balance
-balloons!
-banana
-bandwagoning
-b)).ap(f
-bar...it
-barren
-barrier
-bartenders
-baseless
-basename
-bathwater
-battle
-bearing
-beatnik
-beautiful
-became
-because
-become
-been!—should
-before
-#beginners
-behalf
-behave
-behind
-behold
-belabor
-believe
-belong
-bending
-beneath
-beneficial
-benkort
-besides
-betrays
-better
-between
-beware
-beyond
-bibliography
-bigger
-bigint
-big-integer
-bikeshedding
-billion
-binaries
-binding
-bind(this
-bioinformatics
-bitand
-bitten
-bitwise
-bitxor
-bizarrely
-bjarne
-blaring
-bleeding
-blogcontroller
-blog({}).fork
-blogpage
-blog’s
-bloody
-bludgeon
-blurp_blurp
-boasting
-bodies
-
-body)?
-boiler
-boldly
-bolster
-bonafide
---book
-bookkeeping
-book
-book's
-boolean
-boring
-borrow
-bothering
-both_float
-bottle
-bottom
-bounce
-box
-&box
-box
-box
-box`
-box
-br#"..."#
-br##"..."##
-bracket
-branch
-
-
-breath
-brendan
-brevity
-bridge
-brilliant
-brittle
-broke!
-brought
-browser
-bs.concat(b
-btn,btnname
-btnname
-btreemap
-bubble
-bucket
-buckle
-buddhist
-buffer
-bugger
-![build
-bullseye
-bundle
-burden
-buried
-burritos
-business
-busted
-but...!?
-butterfingered
-butthisis
-
-
-camelcase
-campaign
-camping
-cancelevt(evt
-candidate
-candle
-can_hold
-cannot
-canonical
-capabilities
-capacity
-capital
-c][app-c]
-void constraints ()
-`,
- 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。你会注意到一个*可设计的*状态指示器已经出现在自定义类部分。
-
-`,
- shouldMatch: false,
- },
- {
- name: `invalid_designable`,
- data: `<h3 id="ibdesignable-x-paintcode:0b699a3cd6d609650a3fca90a5cd32cc">IBDesignable x PaintCode</h3>`,
- shouldMatch: false,
- },
- {
- name: `invalid_designable`,
- data: `
-
-
- `,
- 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
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/signalwire/signalwire.go b/pkg/detectors/signalwire/signalwire.go
deleted file mode 100644
index 85ee2b9ccf37..000000000000
--- a/pkg/detectors/signalwire/signalwire.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package signalwire
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"signalwire"}) + `\b([0-9A-Za-z]{50})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"signalwire"}) + `\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\b`)
- urlPat = regexp.MustCompile(`\b([0-9a-z-]{3,64}\.signalwire\.com)\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{"signalwire"}
-}
-
-// FromData will find and optionally verify Signalwire secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
- urlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
- resID := strings.TrimSpace(idMatch[1])
-
- for _, urlMatch := range urlMatches {
- resURL := strings.TrimSpace(urlMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Signalwire,
- Raw: []byte(resMatch),
- }
-
- if verify {
- data := fmt.Sprintf("%s:%s", resID, resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s/api/laml/2010-04-01/Accounts", resURL), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Signalwire
-}
-
-func (s Scanner) Description() string {
- return "SignalWire is a communications platform as a service (CPaaS) provider. SignalWire credentials can be used to access and manage communication services such as voice, messaging, and video."
-}
diff --git a/pkg/detectors/signalwire/signalwire_integration_test.go b/pkg/detectors/signalwire/signalwire_integration_test.go
deleted file mode 100644
index 02f776f04ec2..000000000000
--- a/pkg/detectors/signalwire/signalwire_integration_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package signalwire
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSignalwire_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- id := testSecrets.MustGetField("SIGNALWIRE_ID")
- secret := testSecrets.MustGetField("SIGNALWIRE_TOKEN")
- url := testSecrets.MustGetField("SIGNALWIRE_URL")
- inactiveSecret := testSecrets.MustGetField("SIGNALWIRE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a signalwire secret %s within signalwire url %s and signalwire id %s", secret, url, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Signalwire,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a signalwire secret %s within signalwire url %s and signalwire id %s but not valid", inactiveSecret, url, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Signalwire,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Signalwire.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Signalwire.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/signalwire/signalwire_test.go b/pkg/detectors/signalwire/signalwire_test.go
deleted file mode 100644
index b4f373916929..000000000000
--- a/pkg/detectors/signalwire/signalwire_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package signalwire
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "rBqctDvrJyZ1MAj2aKHpi6wGcPiCzGtEbCSqtE46RB7yVCywVA"
- invalidKey = "rBqctDvrJyZ1MAj2aKHpi6wGc?iCzGtEbCSqtE46RB7yVCywVA"
- validId = "dy0wgsgd-byq5-iqfb-ez1d-dvjepkyub9n9"
- invalidId = "dy0wgsgd?byq5-iqfb-ez1d-dvjepkyub9n9"
- validUrl = "09ft358n4gpa1c.signalwire.com"
- invalidUrl = "09ft358n4gpa1c?signalwire.com"
- keyword = "signalwire"
-)
-
-func TestSignalwire_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword signalwire",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId, keyword, validUrl),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId, keyword, invalidUrl),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/signaturit/signaturit.go b/pkg/detectors/signaturit/signaturit.go
deleted file mode 100644
index d71657a3b4ce..000000000000
--- a/pkg/detectors/signaturit/signaturit.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package signaturit
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"signaturit"}) + `\b([0-9A-Za-z]{86})\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{"signaturit"}
-}
-
-// FromData will find and optionally verify Signaturit secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Signaturit,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.sandbox.signaturit.com/v3/signatures.json", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Signaturit
-}
-
-func (s Scanner) Description() string {
- return "Signaturit is a service for electronic signatures. Signaturit API keys can be used to access and manage signature requests and related data."
-}
diff --git a/pkg/detectors/signaturit/signaturit_integration_test.go b/pkg/detectors/signaturit/signaturit_integration_test.go
deleted file mode 100644
index b6670237d705..000000000000
--- a/pkg/detectors/signaturit/signaturit_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package signaturit
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSignaturit_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SIGNATURIT")
- inactiveSecret := testSecrets.MustGetField("SIGNATURIT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a signaturit secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Signaturit,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a signaturit secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Signaturit,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Signaturit.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Signaturit.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/signaturit/signaturit_test.go b/pkg/detectors/signaturit/signaturit_test.go
deleted file mode 100644
index 2175d80af6c5..000000000000
--- a/pkg/detectors/signaturit/signaturit_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package signaturit
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "4AgnWjwDhcgHULFkDkQCY7bra8XfsV9l72JYqsIAFnE9mEo6VusS5yKBJq3OSTEyJfsPoPhorAUc9yUiCxCIxz"
- invalidPattern = "4AgnWjwDhcgHULFkDkQCY7bra8XfsV?l72JYqsIAFnE9mEo6VusS5yKBJq3OSTEyJfsPoPhorAUc9yUiCxCIxz"
- keyword = "signaturit"
-)
-
-func TestSignaturit_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword signaturit",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/signupgenius/signupgenius.go b/pkg/detectors/signupgenius/signupgenius.go
deleted file mode 100644
index 548d46e9fc18..000000000000
--- a/pkg/detectors/signupgenius/signupgenius.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package signupgenius
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"signupgenius"}) + `\b([0-9A-Za-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"signupgenius"}
-}
-
-// FromData will find and optionally verify Signupgenius secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Signupgenius,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.signupgenius.com/v2/k/user/profile/?user_key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Signupgenius
-}
-
-func (s Scanner) Description() string {
- return "SignupGenius is an online tool for creating and managing sign-up forms for events and activities. SignupGenius API keys can be used to access and manage these forms and related data."
-}
diff --git a/pkg/detectors/signupgenius/signupgenius_integration_test.go b/pkg/detectors/signupgenius/signupgenius_integration_test.go
deleted file mode 100644
index e74b007c37c8..000000000000
--- a/pkg/detectors/signupgenius/signupgenius_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package signupgenius
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSignupgenius_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SIGNUPGENIUS")
- inactiveSecret := testSecrets.MustGetField("SIGNUPGENIUS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a signupgenius secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Signupgenius,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a signupgenius secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Signupgenius,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Signupgenius.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Signupgenius.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/signupgenius/signupgenius_test.go b/pkg/detectors/signupgenius/signupgenius_test.go
deleted file mode 100644
index eacba7d8e042..000000000000
--- a/pkg/detectors/signupgenius/signupgenius_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package signupgenius
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "kARx6uGgPqBkho0jzPNfgnAduN3hDjBY"
- invalidPattern = "kARx6uGgPqBkho0j?PNfgnAduN3hDjBY"
- keyword = "signupgenius"
-)
-
-func TestSignupgenius_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword signupgenius",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sigopt/sigopt.go b/pkg/detectors/sigopt/sigopt.go
deleted file mode 100644
index e24ea867882d..000000000000
--- a/pkg/detectors/sigopt/sigopt.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package sigopt
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sigopt"}) + `\b([A-Z0-9]{48})\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{"sigopt"}
-}
-
-// FromData will find and optionally verify Sigopt secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Sigopt,
- Raw: []byte(resMatch),
- }
-
- if verify {
- data := fmt.Sprintf("%s:", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.sigopt.com/v1/experiments", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Sigopt
-}
-
-func (s Scanner) Description() string {
- return "Sigopt is a platform for optimizing machine learning models. Sigopt API keys can be used to access and manage experiments on the Sigopt platform."
-}
diff --git a/pkg/detectors/sigopt/sigopt_integration_test.go b/pkg/detectors/sigopt/sigopt_integration_test.go
deleted file mode 100644
index c25a2cc62a9f..000000000000
--- a/pkg/detectors/sigopt/sigopt_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package sigopt
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSigopt_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SIGOPT")
- inactiveSecret := testSecrets.MustGetField("SIGOPT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sigopt secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sigopt,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sigopt secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sigopt,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Sigopt.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Sigopt.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/sigopt/sigopt_test.go b/pkg/detectors/sigopt/sigopt_test.go
deleted file mode 100644
index a0053185fc93..000000000000
--- a/pkg/detectors/sigopt/sigopt_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package sigopt
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "GF67UZ5P7UX2A6ELCAE11YDCOYJABCBDYK26O76HRIPU294T"
- invalidPattern = "GF67UZ5P7UX2A6ELCAE11YDC?YJABCBDYK26O76HRIPU294T"
- keyword = "sigopt"
-)
-
-func TestSigopt_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword sigopt",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/simfin/simfin.go b/pkg/detectors/simfin/simfin.go
deleted file mode 100644
index e356197a80b6..000000000000
--- a/pkg/detectors/simfin/simfin.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package simfin
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"simfin"}) + `\b([a-zA-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"simfin"}
-}
-
-// FromData will find and optionally verify SimFin secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SimFin,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://simfin.com/api/v2/companies/list?api-key=%s", resMatch), nil)
- if err != nil {
- continue
- }
-
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- bodyString := string(bodyBytes)
- validResponse := !strings.Contains(bodyString, `"error"`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 && validResponse {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SimFin
-}
-
-func (s Scanner) Description() string {
- return "SimFin provides financial data and APIs for accessing this data. SimFin API keys can be used to access and retrieve financial data."
-}
diff --git a/pkg/detectors/simfin/simfin_integration_test.go b/pkg/detectors/simfin/simfin_integration_test.go
deleted file mode 100644
index 999febb9d948..000000000000
--- a/pkg/detectors/simfin/simfin_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package simfin
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSimFin_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SIMFIN")
- inactiveSecret := testSecrets.MustGetField("SIMFIN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a simfin secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SimFin,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a simfin secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SimFin,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SimFin.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SimFin.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/simfin/simfin_test.go b/pkg/detectors/simfin/simfin_test.go
deleted file mode 100644
index 66972104b3cd..000000000000
--- a/pkg/detectors/simfin/simfin_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package simfin
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "Y28WVaw01VjEEZQ6zn4HvmfpEWT70nPC"
- invalidPattern = "Y28WVaw01VjEEZQ6?n4HvmfpEWT70nPC"
- keyword = "simfin"
-)
-
-func TestSimFin_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword simfin",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/simplesat/simplesat.go b/pkg/detectors/simplesat/simplesat.go
deleted file mode 100644
index 83418e2c79fb..000000000000
--- a/pkg/detectors/simplesat/simplesat.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package simplesat
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"simplesat"}) + `\b([a-z0-9]{40})`)
-)
-
-// 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{"simplesat"}
-}
-
-// FromData will find and optionally verify Simplesat secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Simplesat,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.simplesat.io/api/answers/", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-Simplesat-Token", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Simplesat
-}
-
-func (s Scanner) Description() string {
- return "Simplesat is a customer satisfaction survey tool. Simplesat API keys can be used to access and manage customer feedback data."
-}
diff --git a/pkg/detectors/simplesat/simplesat_integration_test.go b/pkg/detectors/simplesat/simplesat_integration_test.go
deleted file mode 100644
index e00c0f31b133..000000000000
--- a/pkg/detectors/simplesat/simplesat_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package simplesat
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSimplesat_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SIMPLESAT_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SIMPLESAT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a simplesat secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Simplesat,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a simplesat secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Simplesat,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Simplesat.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Simplesat.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/simplesat/simplesat_test.go b/pkg/detectors/simplesat/simplesat_test.go
deleted file mode 100644
index 89f4291d9931..000000000000
--- a/pkg/detectors/simplesat/simplesat_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package simplesat
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "52dfk83ssx3hnz3ke35jqqucwy5ojdtyd0at5v4c"
- invalidPattern = "52dfk83ssx3hnz3ke35j?qucwy5ojdtyd0at5v4c"
- keyword = "simplesat"
-)
-
-func TestSimplesat_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword simplesat",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/simplynoted/simplynoted.go b/pkg/detectors/simplynoted/simplynoted.go
deleted file mode 100644
index ecbb98e1edba..000000000000
--- a/pkg/detectors/simplynoted/simplynoted.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package simplynoted
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"simplynoted"}) + `\b([a-zA-Z0-9\S]{340,360})\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{"simplynoted"}
-}
-
-// FromData will find and optionally verify SimplyNoted secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SimplyNoted,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.simplynoted.com/api/products", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SimplyNoted
-}
-
-func (s Scanner) Description() string {
- return "SimplyNoted is a service used for sending personalized handwritten notes. SimplyNoted API keys can be used to access and send these notes programmatically."
-}
diff --git a/pkg/detectors/simplynoted/simplynoted_integration_test.go b/pkg/detectors/simplynoted/simplynoted_integration_test.go
deleted file mode 100644
index dff91caa7eca..000000000000
--- a/pkg/detectors/simplynoted/simplynoted_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package simplynoted
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSimplyNoted_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SIMPLYNOTED")
- inactiveSecret := testSecrets.MustGetField("SIMPLYNOTED_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a simplynoted secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SimplyNoted,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a simplynoted secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SimplyNoted,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SimplyNoted.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SimplyNoted.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/simplynoted/simplynoted_test.go b/pkg/detectors/simplynoted/simplynoted_test.go
deleted file mode 100644
index 0e7f3087f72f..000000000000
--- a/pkg/detectors/simplynoted/simplynoted_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package simplynoted
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "NSPwR0>Q,wTEJcGyFF)bvY#FACMwu9DzLFD|bLC@D2d1-zILY&sPo6N_AhOwD(:N0`Td[NL2dZt;>yzn3yN'5x[ia&0v2&M0D_r0!L3#dt:Mx^IdcL{xSBIlyQ4jd*=4U2=gdr%2ZDvQqxGaX1WAnoZy2Ny20rnl^f8P/Y.u`jxsOy%2iesTS&u|7SMQ]wJ*f2c?lQ:o4&X(/[y.ZK%2Av100u($ZeTU2N4yCFgKp5PqicqSkgjIla31uGS0OvpmpSiy@rFvthHA,k&)uRAM6$>#dt:Mx^IdcL{xSBIlyQ4jd*=4U2=gdr%2ZDvQqxGJcGyFF)bvY#FACMwu9DzL"
- invalidPattern = "N PwR0>Q,wTEJcGyFF)bvY#FACMwu9DzLFD|bLC@D2d1-zILY&sPo6N_AhOwD(:N0`Td[NL2dZt;>yzn3yN'5x[ia&0v2&M0D_r0!L3#dt:Mx^IdcL{xSBIlyQ4jd*=4U2=gdr%2ZDvQqxGaX1WAnoZy2Ny20rnl^f8P/Y.u`jxsOy%2iesTS&u|7SMQ]wJ*f2c?lQ:o4&X(/[y.ZK%2Av100u($ZeTU2N4yCFgKp5PqicqSkgjIla31uGS0OvpmpSiy@rFvthHA,k&)uRAM6$>#dt:Mx^IdcL{xSBIlyQ4jd*=4U2=gdr%2ZDvQqxGJcGyFF)bvY#FACMwu9DzL"
- keyword = "simplynoted"
-)
-
-func TestSimplyNoted_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword simplynoted",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/simvoly/simvoly.go b/pkg/detectors/simvoly/simvoly.go
deleted file mode 100644
index 12936e5895d5..000000000000
--- a/pkg/detectors/simvoly/simvoly.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package simvoly
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"simvoly"}) + `\b([a-z0-9]{33})\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{"simvoly"}
-}
-
-// FromData will find and optionally verify Simvoly secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Simvoly,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://rendyplayground.simvoly.com/api/site/members?group_id=12&limit=25&skip=25", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Simvoly
-}
-
-func (s Scanner) Description() string {
- return "Simvoly is a platform for building websites and online stores. Simvoly API keys can be used to access and manage site data and functionalities."
-}
diff --git a/pkg/detectors/simvoly/simvoly_integration_test.go b/pkg/detectors/simvoly/simvoly_integration_test.go
deleted file mode 100644
index af28be91ae18..000000000000
--- a/pkg/detectors/simvoly/simvoly_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package simvoly
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSimvoly_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SIMVOLY_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SIMVOLY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a simvoly secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Simvoly,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a simvoly secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Simvoly,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Simvoly.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Simvoly.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/simvoly/simvoly_test.go b/pkg/detectors/simvoly/simvoly_test.go
deleted file mode 100644
index fc10692dc409..000000000000
--- a/pkg/detectors/simvoly/simvoly_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package simvoly
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "zu0rpr33yvk0c18vs5wzpx2nq9ww1bh2b"
- invalidPattern = "zu0rpr33yvk0c18v?5wzpx2nq9ww1bh2b"
- keyword = "simvoly"
-)
-
-func TestSimvoly_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword simvoly",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sinchmessage/sinchmessage.go b/pkg/detectors/sinchmessage/sinchmessage.go
deleted file mode 100644
index 5d21481cc285..000000000000
--- a/pkg/detectors/sinchmessage/sinchmessage.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package sinchmessage
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sinch"}) + `\b([a-z0-9]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sinch"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"sinch"}
-}
-
-// FromData will find and optionally verify SinchMessage secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SinchMessage,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`
- {
- "from": "447537454435",
- "to": [ "639668957581" ],
- "body": "This is a test message from your Sinch account"
- }`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://sms.api.sinch.com/xms/v1/"+resIdMatch+"/batches", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SinchMessage
-}
-
-func (s Scanner) Description() string {
- return "Sinch is a cloud-based communications platform that enables businesses to send and receive messages, calls, and other communications. Sinch API keys can be used to access and manage these communication services."
-}
diff --git a/pkg/detectors/sinchmessage/sinchmessage_integration_test.go b/pkg/detectors/sinchmessage/sinchmessage_integration_test.go
deleted file mode 100644
index a4017882038e..000000000000
--- a/pkg/detectors/sinchmessage/sinchmessage_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package sinchmessage
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSinchMessage_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SINCHMESSAGE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SINCHMESSAGE_INACTIVE")
- id := testSecrets.MustGetField("SINCHMESSAGE_PLANID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sinchmessage secret %s within sinchid %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SinchMessage,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sinchmessage secret %s within but sinchid %s not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SinchMessage,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SinchMessage.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SinchMessage.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/sinchmessage/sinchmessage_test.go b/pkg/detectors/sinchmessage/sinchmessage_test.go
deleted file mode 100644
index 53035bb0a3b8..000000000000
--- a/pkg/detectors/sinchmessage/sinchmessage_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package sinchmessage
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "k255nuzmx8fzlkjfjz5unlrdbd658bon"
- invalidKey = "k255nuzmx8fzlk?fjz5unlrdbd658bon"
- validId = "y9yvuc6hl2up5qyohet77j23ntll7pr7"
- invalidId = "y9yvuc6?l2up5qyohet77j23ntll7pr7"
- keyword = "sinchmessage"
-)
-
-func TestSinchMessage_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword sinchmessage",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validKey, validId, validKey, validId},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sirv/sirv.go b/pkg/detectors/sirv/sirv.go
deleted file mode 100644
index 0f80dd963dc6..000000000000
--- a/pkg/detectors/sirv/sirv.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package sirv
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sirv"}) + `\b([a-zA-Z0-9\S]{88})`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sirv"}) + `\b([a-zA-Z0-9]{26})\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{"sirv"}
-}
-
-// FromData will find and optionally verify Sirv secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- resIdMatch := strings.TrimSpace(idMatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Sirv,
- Raw: []byte(resMatch),
- }
-
- if verify {
- timeout := 10 * time.Second
- client.Timeout = timeout
- payload := strings.NewReader(fmt.Sprintf(`{"clientId":"%s","clientSecret":"%s"}`, resIdMatch, resMatch))
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.sirv.com/v2/token", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Sirv
-}
-
-func (s Scanner) Description() string {
- return "Sirv is a media management service used for image optimization and delivery. Sirv API keys can be used to access and manage media files."
-}
diff --git a/pkg/detectors/sirv/sirv_integration_test.go b/pkg/detectors/sirv/sirv_integration_test.go
deleted file mode 100644
index 1905f6a4fddd..000000000000
--- a/pkg/detectors/sirv/sirv_integration_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package sirv
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSirv_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SIRV")
- id := testSecrets.MustGetField("SIRV_ID")
- inactiveSecret := testSecrets.MustGetField("SIRV_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sirv secret %s within sirv %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sirv,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sirv secret %s within sirv %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sirv,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Sirv.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Sirv.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/sirv/sirv_test.go b/pkg/detectors/sirv/sirv_test.go
deleted file mode 100644
index 3b17bc099711..000000000000
--- a/pkg/detectors/sirv/sirv_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package sirv
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "Ivus86]AJDk:rghv;8,o00SVP0efds5BBh453jew~mnC$K8elZI-OGU^v0AA#`>vek1cKlkipm]!n/f1xTDlUn1D"
- invalidKey = "?vus86]AJDk:rghv;8,o00SVP0efds5BBh453jew~mnC$K8elZI-OGU^v0AA#`>vek1cKlkipm]!n/f1xTDlUn1D"
- validId = "n64cjK3Dg9Xyr2icHYypLwXl6V"
- invalidId = "n64cjK3Dg9Xyr?icHYypLwXl6V"
- keyword = "sirv"
-)
-
-func TestSirv_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword sirv",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/siteleaf/siteleaf.go b/pkg/detectors/siteleaf/siteleaf.go
deleted file mode 100644
index 707118bdd3f3..000000000000
--- a/pkg/detectors/siteleaf/siteleaf.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package siteleaf
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"siteleaf"}) + `\b([0-9Aa-z]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"siteleaf"}) + `\b([0-9Aa-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"siteleaf"}
-}
-
-// FromData will find and optionally verify Siteleaf secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Siteleaf,
- Raw: []byte(resMatch),
- }
-
- if verify {
-
- data := fmt.Sprintf("%s:%s", resIdMatch, resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.siteleaf.com/v2/sites", nil)
- if err != nil {
- continue
- }
-
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Siteleaf
-}
-
-func (s Scanner) Description() string {
- return "Siteleaf is a content management system (CMS) designed for managing websites. Siteleaf API keys can be used to access and modify site content and settings."
-}
diff --git a/pkg/detectors/siteleaf/siteleaf_integration_test.go b/pkg/detectors/siteleaf/siteleaf_integration_test.go
deleted file mode 100644
index e55d124a5aa6..000000000000
--- a/pkg/detectors/siteleaf/siteleaf_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package siteleaf
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSiteleaf_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SITELEAF_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SITELEAF_INACTIVE")
- id := testSecrets.MustGetField("SITELEAF_USERID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a siteleafkey secret %s within siteleafid %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Siteleaf,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a siteleafkey secret %s within siteleafid %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Siteleaf,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Siteleaf.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Siteleaf.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/siteleaf/siteleaf_test.go b/pkg/detectors/siteleaf/siteleaf_test.go
deleted file mode 100644
index 1f1f716b96c3..000000000000
--- a/pkg/detectors/siteleaf/siteleaf_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package siteleaf
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "lklep3linr2itd42nkncw9b8s9g1yrgb"
- invalidKey = "lklep3li?r2itd42nkncw9b8s9g1yrgb"
- validId = "zoc07j72vs6q1lxs6j5vkzz7mefhiii6"
- invalidId = "zoc07j72vs?q1lxs6j5vkzz7mefhiii6"
- keyword = "siteleaf"
-)
-
-func TestSiteleaf_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword siteleaf",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validKey, validId, validKey, validId},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/skrappio/skrappio.go b/pkg/detectors/skrappio/skrappio.go
deleted file mode 100644
index 4e26a89098ef..000000000000
--- a/pkg/detectors/skrappio/skrappio.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package skrappio
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"skrapp"}) + `\b([a-z0-9A-Z]{42})\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{"skrapp"}
-}
-
-// FromData will find and optionally verify Skrapio secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Skrappio,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.skrapp.io/api/v2/account", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-Access-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Skrappio
-}
-
-func (s Scanner) Description() string {
- return "Skrapio is a service used for email verification and enrichment. Skrapio API keys can be used to access and utilize its services for verifying and enriching email addresses."
-}
diff --git a/pkg/detectors/skrappio/skrappio_integration_test.go b/pkg/detectors/skrappio/skrappio_integration_test.go
deleted file mode 100644
index 709f782f78f7..000000000000
--- a/pkg/detectors/skrappio/skrappio_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package skrappio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSkrapio_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SKRAPIO_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SKRAPIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a skrapp secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Skrappio,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a skrapp secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Skrappio,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Skrapio.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Skrapio.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/skrappio/skrappio_test.go b/pkg/detectors/skrappio/skrappio_test.go
deleted file mode 100644
index ee8222cfd1f7..000000000000
--- a/pkg/detectors/skrappio/skrappio_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package skrappio
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "rAheANMWIEHz9GfmtocXjvG8ESfdZvgSMTbDBIR7SA"
- invalidPattern = "rAheANMWIEHz9GfmtocXj?G8ESfdZvgSMTbDBIR7SA"
- keyword = "skrappio"
-)
-
-func TestSkrapio_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword skrappio",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/skybiometry/skybiometry.go b/pkg/detectors/skybiometry/skybiometry.go
deleted file mode 100644
index 6eeddbc03217..000000000000
--- a/pkg/detectors/skybiometry/skybiometry.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package skybiometry
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"skybiometry"}) + `\b([0-9a-z]{25,26})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"skybiometry"}) + `\b([0-9a-z]{25,26})\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{"skybiometry"}
-}
-
-// FromData will find and optionally verify SkyBiometry secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueKeyMatches, uniqueSecretMatches := make(map[string]struct{}), make(map[string]struct{})
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeyMatches[match[1]] = struct{}{}
- }
-
- for _, match := range secretPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueSecretMatches[match[1]] = struct{}{}
- }
-
- for key := range uniqueKeyMatches {
- for secret := range uniqueSecretMatches {
- if key == secret {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SkyBiometry,
- Raw: []byte(secret),
- }
-
- if verify {
- isVerified, verificationErr := verifySkyBiometery(ctx, client, key, secret)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SkyBiometry
-}
-
-func (s Scanner) Description() string {
- return "SkyBiometry is a facial recognition service. SkyBiometry API keys can be used to access and utilize their facial recognition API."
-}
-
-func verifySkyBiometery(ctx context.Context, client *http.Client, apiKey, apiSecret string) (bool, error) {
- apiURL := fmt.Sprintf("https://api.skybiometry.com/fc/account/authenticate?api_key=%s&api_secret=%s", apiKey, apiSecret)
-
- req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusBadRequest, http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/skybiometry/skybiometry_integration_test.go b/pkg/detectors/skybiometry/skybiometry_integration_test.go
deleted file mode 100644
index f7d96e29b11f..000000000000
--- a/pkg/detectors/skybiometry/skybiometry_integration_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package skybiometry
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSkyBiometry_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- key := testSecrets.MustGetField("SKYBIOMETRY_KEY")
- inactiveKey := testSecrets.MustGetField("SKYBIOMETRY_KEY_INACTIVE")
- secret := testSecrets.MustGetField("SKYBIOMETRY")
- inactiveSecret := testSecrets.MustGetField("SKYBIOMETRY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a skybiometry key %s with skybiometry secret %s within", key, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SkyBiometry,
- Verified: true,
- },
- {
- DetectorType: detectorspb.DetectorType_SkyBiometry,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a skybiometry key %s with skybiometry secret %s within but not valid", inactiveKey, inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SkyBiometry,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_SkyBiometry,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SkyBiometry.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SkyBiometry.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/skybiometry/skybiometry_test.go b/pkg/detectors/skybiometry/skybiometry_test.go
deleted file mode 100644
index f052f95e05a8..000000000000
--- a/pkg/detectors/skybiometry/skybiometry_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package skybiometry
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "qdmxigxqpkpo8i38zxfuxct3d2"
- invalidKey = "qdmx?gxqpkpo8i38zxfuxct3d2"
- validSecret = "ko86ttplsbrao397eub88pqniu"
- invalidSecret = "ko86ttplsbrao?97eub88pqniu"
- keyword = "skybiometry"
-)
-
-func TestSkyBiometry_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword skybiometry",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validSecret),
- want: []string{validKey, validSecret},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidSecret),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/slack/slack.go b/pkg/detectors/slack/slack.go
deleted file mode 100644
index 71f4de3954f0..000000000000
--- a/pkg/detectors/slack/slack.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package slack
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
-}
-
-// Check that the Slack scanner implements the SecretScanner interface at compile time.
-var _ detectors.Detector = Scanner{}
-
-var (
- defaultClient = common.SaneHttpClient()
- tokenPats = map[string]*regexp.Regexp{
- "Slack Bot Token": regexp.MustCompile(`xoxb\-[0-9]{10,13}\-[0-9]{10,13}[a-zA-Z0-9\-]*`),
- "Slack User Token": regexp.MustCompile(`xoxp\-[0-9]{10,13}\-[0-9]{10,13}[a-zA-Z0-9\-]*`),
- "Slack Workspace Access Token": regexp.MustCompile(`xoxa\-[0-9]{10,13}\-[0-9]{10,13}[a-zA-Z0-9\-]*`),
- "Slack Workspace Refresh Token": regexp.MustCompile(`xoxr\-[0-9]{10,13}\-[0-9]{10,13}[a-zA-Z0-9\-]*`),
- }
- verifyURL = "https://slack.com/api/auth.test"
-)
-
-type authRes struct {
- Ok bool `json:"ok"`
- URL string `json:"url"` // Workspace URL
- Team string `json:"team"` // Human friendly workspace name
- User string `json:"user"` // Username
- TeamID string `json:"team_id"`
- UserID string `json:"user_id"`
- BotID string `json:"bot_id"`
- Error string `json:"error"`
-}
-
-// 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{"xoxb-", "xoxp-", "xoxa-", "xoxr-"}
-}
-
-// FromData will find and optionally verify Slack secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- for key, tokenPat := range tokenPats {
- tokens := tokenPat.FindAllString(dataStr, -1)
-
- for _, token := range tokens {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Slack,
- Raw: []byte(token),
- }
- s1.ExtraData = map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/slack/",
- "token_type": key,
- }
- if verify {
- client := s.client
- if s.client == nil {
- client = defaultClient
- }
-
- req, err := http.NewRequestWithContext(ctx, "POST", verifyURL, nil)
- if err != nil {
- continue
- }
-
- req.Header.Add("Content-Type", "application/json; charset=utf-8")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- var authResponse authRes
- if err := json.NewDecoder(res.Body).Decode(&authResponse); err != nil {
- err = fmt.Errorf("failed to decode auth response: %w", err)
- s1.SetVerificationError(err, token)
- }
-
- if authResponse.Ok {
- s1.Verified = true
- // Store name of user and team in extra data received from slack's api
- s1.ExtraData["team"] = authResponse.Team
- s1.ExtraData["name"] = authResponse.User
- // Slack API returns 200 even if the token is invalid. We need to check the error field.
- } else if authResponse.Error == "invalid_auth" {
- // The secret is determinately not verified (nothing to do)
- } else if authResponse.Error == "account_inactive" {
- // "Authentication token is for a deleted user or workspace when using a bot token."
- // https://api.slack.com/methods/auth.test) (Per
- // https://slack.com/help/articles/360000446446-Manage-deactivated-members-apps-and-integrations,
- // reactivating a bot regenerates its tokens, so this candidate is determinately unverified.)
- } else if authResponse.Error == "token_revoked" {
- // "Authentication token is for a deleted user or workspace, or the app has been removed when using a user token."
- // This indicates the token is no longer valid and determinately unverified.
- // https://api.slack.com/methods/auth.test
- } else {
- err = fmt.Errorf("unexpected error auth response %+v", authResponse.Error)
- s1.SetVerificationError(err, token)
- }
- } else {
- s1.SetVerificationError(err, token)
- }
- s1.AnalysisInfo = map[string]string{
- "key": token,
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Slack
-}
-
-func (s Scanner) Description() string {
- return "Slack tokens can be used to authenticate API requests to the Slack platform, allowing access to various workspace resources and functionalities."
-}
diff --git a/pkg/detectors/slack/slack_integration_test.go b/pkg/detectors/slack/slack_integration_test.go
deleted file mode 100644
index 2f3f8539d9c2..000000000000
--- a/pkg/detectors/slack/slack_integration_test.go
+++ /dev/null
@@ -1,210 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package slack
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSlack_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SLACK")
- secretInactive := testSecrets.MustGetField("SLACK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
-
- tests := []struct {
- name string
- s Scanner
- args args
- wantResults []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slack secret %s within", secret)),
- verify: true,
- },
- wantResults: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Slack,
- Verified: true,
- ExtraData: map[string]string{
- "name": "marge.haskell.bridge",
- "rotation_guide": "https://howtorotate.com/docs/tutorials/slack/",
- "team": "ct.org",
- "token_type": "Slack User Token",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found but unverified",
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slack secret %s within", secretInactive)),
- verify: true,
- },
- wantResults: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Slack,
- Verified: false,
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/slack/",
- "token_type": "Slack User Token",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "account_inactive",
- s: Scanner{client: common.ConstantResponseHttpClient(200, `{"ok": false, "error": "account_inactive"}`)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slack secret %s within", secret)),
- verify: true,
- },
- wantResults: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Slack,
- Verified: false,
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/slack/",
- "token_type": "Slack User Token",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "token_revoked",
- s: Scanner{client: common.ConstantResponseHttpClient(200, `{"ok": false, "error": "token_revoked"}`)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slack secret %s within", secret)),
- verify: true,
- },
- wantResults: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Slack,
- Verified: false,
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/slack/",
- "token_type": "Slack User Token",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slack secret %s within", secret)),
- verify: true,
- },
- wantResults: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Slack,
- Verified: false,
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/slack/",
- "token_type": "Slack User Token",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "unexpected auth response",
- s: Scanner{client: common.ConstantResponseHttpClient(200, `{"ok": false, "error": "unexpected_error"}`)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slack secret %s within", secret)),
- verify: true,
- },
- wantResults: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Slack,
- Verified: false,
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/slack/",
- "token_type": "Slack User Token",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Slack.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- got[i].AnalysisInfo = nil
-
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.wantResults, ignoreOpts); diff != "" {
- t.Errorf("Slack.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/slack/slack_test.go b/pkg/detectors/slack/slack_test.go
deleted file mode 100644
index 49f8e3c2e2ee..000000000000
--- a/pkg/detectors/slack/slack_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package slack
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validBotToken = "xoxb-65677559833-9613778673399u"
- invalidBotToken = "xoxb-65677559833?9613778673399u"
- validUserToken = "xoxp-24232636415-0285024315463c"
- invalidUserToken = "xoxp-24232636415?0285024315463c"
- validWorkspaceAccessToken = "xoxa-08532509747-07570405353c"
- invalidWorkspaceAccessToken = "xoxa-08532509747?07570405353c"
- validWorkspaceRefreshToken = "xoxr-833485595373-24619897332l"
- invalidWorkspaceRefreshToken = "xoxr-833485595373?24619897332l"
- keyword = "slack"
-)
-
-func TestSlack_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword slack",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, validBotToken, keyword, validUserToken, keyword, validWorkspaceAccessToken, keyword, validWorkspaceRefreshToken),
- want: []string{validBotToken, validUserToken, validWorkspaceAccessToken, validWorkspaceRefreshToken},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, invalidBotToken, keyword, invalidUserToken, keyword, invalidWorkspaceAccessToken, keyword, invalidWorkspaceRefreshToken),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/slackwebhook/slackwebhook.go b/pkg/detectors/slackwebhook/slackwebhook.go
deleted file mode 100644
index e151f4a9ca40..000000000000
--- a/pkg/detectors/slackwebhook/slackwebhook.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package slackwebhook
-
-import (
- "bytes"
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- regexp "github.com/wasilibs/go-re2"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)
-
-var (
- defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPats = map[string]*regexp.Regexp{
- "Slack Service Web Hook": regexp.MustCompile(`(https://hooks\.slack\.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[A-Za-z0-9]{23,25})`),
- "Slack Workflow Web Hook ": regexp.MustCompile(`(https://hooks\.slack\.com/workflows/T[A-Z0-9]+/A[A-Z0-9]+/[0-9]{17,19}/[A-Za-z0-9]{23,25})`),
- }
-)
-
-// 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{"hooks.slack.com"}
-}
-
-// FromData will find and optionally verify SlackWebhook secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- for _, keyPat := range keyPats {
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SlackWebhook,
- Raw: []byte(resMatch),
- }
- s1.ExtraData = map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/",
- }
-
- if verify {
-
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- // We don't want to actually send anything to webhooks we find. To verify them without spamming them, we
- // send an intentionally malformed message and look for a particular expected error message.
- payload := strings.NewReader(`intentionally malformed JSON from TruffleHog scan`)
- req, err := http.NewRequestWithContext(ctx, "POST", resMatch, payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
-
- defer res.Body.Close()
-
- switch {
- case res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusMultipleChoices:
- // Hopefully this never happens - it means we actually sent something to a channel somewhere. But
- // we at least know the secret is verified.
- s1.Verified = true
- case res.StatusCode == http.StatusBadRequest && bytes.Equal(bodyBytes, []byte("invalid_payload")):
- s1.Verified = true
- case res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusForbidden:
- // Not a real webhook or the owning app's OAuth token has been revoked or the app has been deleted
- // You might want to handle this case or log it.
- default:
- err = fmt.Errorf("unexpected HTTP response status %d: %s", res.StatusCode, bodyBytes)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SlackWebhook
-}
-
-func (s Scanner) Description() string {
- return "Slack webhooks are used to send messages from external sources into Slack channels. If compromised, they can be used to send unauthorized messages."
-}
-
-func (s Scanner) IsFalsePositive(result detectors.Result) (bool, string) {
- // ignore "https:" as a false positive for slack webhook detector
- if strings.Contains(string(result.Raw), "https:") {
- return false, ""
- }
-
- // back to the default false positive checks
- return detectors.IsKnownFalsePositive(string(result.Raw), detectors.DefaultFalsePositives, true)
-
-}
diff --git a/pkg/detectors/slackwebhook/slackwebhook_integration_test.go b/pkg/detectors/slackwebhook/slackwebhook_integration_test.go
deleted file mode 100644
index 706b5b62e25b..000000000000
--- a/pkg/detectors/slackwebhook/slackwebhook_integration_test.go
+++ /dev/null
@@ -1,191 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package slackwebhook
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSlackWebhook_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SLACKWEBHOOK_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SLACKWEBHOOK_INACTIVE")
- deletedWebhook := testSecrets.MustGetField("SLACKWEBHOOK_DELETED")
- deletedUserActiveWebhook := testSecrets.MustGetField("SLACKWEBHOOK_DELETED_USER_ACTIVE_WEBHOOK")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "active webhook",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SlackWebhook,
- ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "active webhook created by a deactivated user",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", deletedUserActiveWebhook)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SlackWebhook,
- ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "deleted webhook",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", deletedWebhook)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SlackWebhook,
- ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "webhook from app with revoked token",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SlackWebhook,
- ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "unexpected webhook response",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "oh no")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SlackWebhook,
- ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SlackWebhook,
- ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SlackWebhook.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("SlackWebhook.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/slackwebhook/slackwebhook_test.go b/pkg/detectors/slackwebhook/slackwebhook_test.go
deleted file mode 100644
index 1a63f0c8641b..000000000000
--- a/pkg/detectors/slackwebhook/slackwebhook_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package slackwebhook
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "https://hooks.slack.com/services/TAGGINGEXAMPLE/BASE/91nziTEEzAAcaNZiz1mPPoXyS"
- invalidPattern = "https://hooks.slack.com/apps/LAGGINGEXAMPLE/BASE/91nziTEEzAAcaNZiz1mPPoXyS"
-)
-
-func TestSlackWebHook_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: invalidPattern,
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/smartsheets/smartsheets.go b/pkg/detectors/smartsheets/smartsheets.go
deleted file mode 100644
index 85216cdf857b..000000000000
--- a/pkg/detectors/smartsheets/smartsheets.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package smartsheets
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sheet"}) + `\b([a-zA-Z0-9]{26}|[a-zA-Z0-9]{37})\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{"smartsheet"}
-}
-
-// FromData will find and optionally verify Smartsheets secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueKeys = make(map[string]struct{})
-
- for _, matche := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeys[matche[1]] = struct{}{}
- }
-
- for key := range uniqueKeys {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Smartsheets,
- Raw: []byte(key),
- }
-
- if verify {
- isVerified, verificationErr := verifySmartSheetsToken(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Smartsheets
-}
-
-func (s Scanner) Description() string {
- return "Smartsheets is a platform for work management and automation. Smartsheets API keys can be used to access and modify data and automate workflows within the platform."
-}
-
-func verifySmartSheetsToken(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.smartsheet.com/2.0/sheets", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/smartsheets/smartsheets_integration_test.go b/pkg/detectors/smartsheets/smartsheets_integration_test.go
deleted file mode 100644
index 386ec16e9650..000000000000
--- a/pkg/detectors/smartsheets/smartsheets_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package smartsheets
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSmartsheets_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SMARTSHEETS_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SMARTSHEETS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a smartsheets secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Smartsheets,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a smartsheets secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Smartsheets,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Smartsheets.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Smartsheets.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/smartsheets/smartsheets_test.go b/pkg/detectors/smartsheets/smartsheets_test.go
deleted file mode 100644
index 160e3bcda1ac..000000000000
--- a/pkg/detectors/smartsheets/smartsheets_test.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package smartsheets
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestSmartsheets_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword smartsheet and sheet",
- input: `
- # do not share these secrets
- # list all sheets
- sheets := getsmartsheet("MVE7zmdxouvunYkowLzaudyX7tvMpkqJ3q52C")
- `,
- want: []string{"MVE7zmdxouvunYkowLzaudyX7tvMpkqJ3q52C"},
- },
- {
- name: "valid pattern - with prefixRegex sheet",
- input: `
- # smartsheet credentials
- sheet_id := "MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d"
- `,
- want: []string{"MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d"},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: `
- # smartsheet duplicate credentials
- sheet_id1 := "MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d"
- sheet_id2 := "MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d"
- `,
- want: []string{"MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d"},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: `
- # below is the smartsheet secret
- # use this secret to list sheets
- # do not share this
-
- sslist := listAll("MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d")
- `,
- want: []string{},
- },
- {
- name: "valid pattern - 26 characters",
- input: `
- # smartsheet credentials
- sheet_token := "fakeiq999fakeecyfake3ifake"
- `,
- want: []string{"fakeiq999fakeecyfake3ifake"},
- },
- {
- name: "valid pattern - 26 and 37 characters",
- input: `
- # smartsheet multiple length credentials
- sheet_token := "fakeiq999fakeecyfake3ifake"
- sheet_token2 := "fakezmdxfakenFAKELzhonda7tvMpkqJ3fake"
- `,
- want: []string{"fakeiq999fakeecyfake3ifake", "fakezmdxfakenFAKELzhonda7tvMpkqJ3fake"},
- },
- {
- name: "invalid pattern - 30 characters",
- input: `
- # smartsheet invalid credentials
- sheet_token := "fakeiq999fakeecyfake3ifakeuiop"
- `,
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: `
- # smartsheet secret
- sheet_id = MVE7?mdxouvunYkowLzaudyX7tvMpkqJ3q52C
- `,
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/smartystreets/smartystreets.go b/pkg/detectors/smartystreets/smartystreets.go
deleted file mode 100644
index df4f0ff90239..000000000000
--- a/pkg/detectors/smartystreets/smartystreets.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package smartystreets
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"smartystreets"}) + `\b([a-zA-Z0-9]{20})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"smartystreets"}) + `\b([a-z0-9-]{36})\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{"smartystreets"}
-}
-
-// FromData will find and optionally verify SmartyStreets secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- resIdMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SmartyStreets,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://us-zipcode.api.smartystreets.com/lookup?auth-id=%s&auth-token=%s&state=CA&zipcode=94035", resIdMatch, resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SmartyStreets
-}
-
-func (s Scanner) Description() string {
- return "SmartyStreets provides address validation services. The detected API keys can be used to access and validate addresses."
-}
diff --git a/pkg/detectors/smartystreets/smartystreets_integration_test.go b/pkg/detectors/smartystreets/smartystreets_integration_test.go
deleted file mode 100644
index a4a34abbe0ac..000000000000
--- a/pkg/detectors/smartystreets/smartystreets_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package smartystreets
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSmartyStreets_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SMARTYSTREETS")
- id := testSecrets.MustGetField("SMARTYSTREETS_ID")
- inactiveSecret := testSecrets.MustGetField("SMARTYSTREETS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a smartystreets secret %s within smartystreets %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SmartyStreets,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a smartystreets secret %s within smartystreets %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SmartyStreets,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SmartyStreets.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SmartyStreets.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/smartystreets/smartystreets_test.go b/pkg/detectors/smartystreets/smartystreets_test.go
deleted file mode 100644
index cd04045f5c0c..000000000000
--- a/pkg/detectors/smartystreets/smartystreets_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package smartystreets
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "UzwkWoJlOqT5q9pXbX2n"
- invalidKey = "UzwkWoJlOq?5q9pXbX2n"
- validId = "pers5g2aa5icwflc1cmqjrs3ij8iqjmsg1x3"
- invalidId = "pers5g2aa5icwflc1c?qjrs3ij8iqjmsg1x3"
- keyword = "smartystreets"
-)
-
-func TestSmartyStreets_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword smartystreets",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/smooch/smooch.go b/pkg/detectors/smooch/smooch.go
deleted file mode 100644
index e825f5b02dbc..000000000000
--- a/pkg/detectors/smooch/smooch.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package smooch
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"smooch"}) + `\b(act_[0-9a-z]{24})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"smooch"}) + `\b([0-9a-zA-Z_-]{86})\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{"smooch"}
-}
-
-// FromData will find and optionally verify Smooch secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, secretMatch := range secretMatches {
- resSecret := strings.TrimSpace(secretMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Smooch,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resSecret),
- }
-
- if verify {
- data := fmt.Sprintf("%s:%s", resMatch, resSecret)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.smooch.io/v2/apps", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.smooch+json; version=3")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Smooch
-}
-
-func (s Scanner) Description() string {
- return "Smooch is a messaging platform that allows businesses to communicate with their customers across various messaging channels. Smooch API keys can be used to access and manage these communications."
-}
diff --git a/pkg/detectors/smooch/smooch_integration_test.go b/pkg/detectors/smooch/smooch_integration_test.go
deleted file mode 100644
index b982b52136c0..000000000000
--- a/pkg/detectors/smooch/smooch_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package smooch
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSmooch_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SMOOCH_SECRET")
- id := testSecrets.MustGetField("SMOOCH_ID")
- inactiveID := testSecrets.MustGetField("SMOOCH_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a smooch secret %s within smooch id %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Smooch,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a smooch secret %s within smooch id %s but not valid", secret, inactiveID)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Smooch,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Smooch.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Smooch.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/smooch/smooch_test.go b/pkg/detectors/smooch/smooch_test.go
deleted file mode 100644
index a68cd36f5b4a..000000000000
--- a/pkg/detectors/smooch/smooch_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package smooch
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "act_svf99w1upfk2syajjx6wxxg9"
- invalidKey = "act_svf99w1upf?2syajjx6wxxg9"
- validSecret = "w2XXiJgDfMJnkKQMPKEF4AzeHnQseEcIsK0C7Gm0icA5PtRM4GIZl82B84n01RJMmkz3aGHtpP_44UXXKhW76L"
- invalidSecret = "w2XXiJgDfMJnkKQMPKEF4AzeHnQseEcIsK0C7Gm0icA?PtRM4GIZl82B84n01RJMmkz3aGHtpP_44UXXKhW76L"
- keyword = "smooch"
-)
-
-func TestSmooch_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword smooch",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validSecret),
- want: []string{validKey + validSecret},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidSecret),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/snipcart/snipcart.go b/pkg/detectors/snipcart/snipcart.go
deleted file mode 100644
index 2dd9f0bdc4ec..000000000000
--- a/pkg/detectors/snipcart/snipcart.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package snipcart
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"snipcart"}) + `\b([0-9A-Za-z_]{75})\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{"snipcart"}
-}
-
-// FromData will find and optionally verify Snipcart secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Snipcart,
- Raw: []byte(resMatch),
- }
-
- if verify {
- data := fmt.Sprintf("%s:", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://app.snipcart.com/api/orders", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Snipcart
-}
-
-func (s Scanner) Description() string {
- return "Snipcart is an easy-to-implement shopping cart platform for developers. Snipcart API keys can be used to access and manage e-commerce functionalities."
-}
diff --git a/pkg/detectors/snipcart/snipcart_integration_test.go b/pkg/detectors/snipcart/snipcart_integration_test.go
deleted file mode 100644
index 35be88a33737..000000000000
--- a/pkg/detectors/snipcart/snipcart_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package snipcart
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSnipcart_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SNIPCART")
- inactiveSecret := testSecrets.MustGetField("SNIPCART_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a snipcart secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Snipcart,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a snipcart secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Snipcart,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Snipcart.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Snipcart.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/snipcart/snipcart_test.go b/pkg/detectors/snipcart/snipcart_test.go
deleted file mode 100644
index a8a754fb8a71..000000000000
--- a/pkg/detectors/snipcart/snipcart_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package snipcart
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "zc62fqbZm4zyVsdPFAcaRhFnSFpO12Lpmas3PU9IK5CJEklhJDeybwRr8c7ibBG3fL3iWoXTHbb"
- invalidPattern = "zc62fqbZm4zyVsdPFAcaRhFnSFpO12Lpmas3P?9IK5CJEklhJDeybwRr8c7ibBG3fL3iWoXTHbb"
- keyword = "snipcart"
-)
-
-func TestSnipcart_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword snipcart",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/snowflake/snowflake.go b/pkg/detectors/snowflake/snowflake.go
deleted file mode 100644
index 138731b40c72..000000000000
--- a/pkg/detectors/snowflake/snowflake.go
+++ /dev/null
@@ -1,200 +0,0 @@
-package snowflake
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
- "unicode"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- // accountIdentifierPat matches Snowflake account identifiers in the format: XXXXXXX-XXXXX
- // Example: ABC1234-EXAMPLE
- accountIdentifierPat = regexp.MustCompile(detectors.PrefixRegex([]string{"account"}) + `\b([a-zA-Z]{7}-[0-9a-zA-Z-_]{1,255}(.privatelink)?)\b`)
- // usernameExclusionPat defines characters that should not be present in usernames
- usernameExclusionPat = `!@#$%^&*{}:<>,.;?()/\+=\s\n`
-)
-
-const (
- timeout = 3 * time.Second
- minPasswordLength = 8
-)
-
-// loginRequest represents the payload for Snowflake's login endpoint.
-type loginRequest struct {
- Data struct {
- LoginName string `json:"LOGIN_NAME"`
- Password string `json:"PASSWORD"`
- AccountName string `json:"ACCOUNT_NAME"`
- } `json:"data"`
-}
-
-type loginResponse struct {
- Success bool `json:"success"`
-}
-
-// 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{"snowflake"}
-}
-
-// meetsSnowflakePasswordRequirements checks if a password meets Snowflake's requirements:
-// - Minimum length of 8 characters
-// - Contains at least one lowercase letter
-// - Contains at least one uppercase letter
-// - Contains at least one number
-func meetsSnowflakePasswordRequirements(password string) bool {
- if len(password) < minPasswordLength {
- return false
- }
-
- var hasLower, hasUpper, hasNumber bool
- for _, char := range password {
- switch {
- case unicode.IsLower(char):
- hasLower = true
- case unicode.IsUpper(char):
- hasUpper = true
- case unicode.IsNumber(char):
- hasNumber = true
- }
-
- if hasLower && hasUpper && hasNumber {
- return true
- }
- }
-
- return false
-}
-
-// FromData will find and optionally verify Snowflake secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- // Find all unique account identifiers
- uniqueAccountMatches := make(map[string]struct{})
- for _, match := range accountIdentifierPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueAccountMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- if len(uniqueAccountMatches) == 0 {
- return nil, nil
- }
-
- usernameRegexState := common.UsernameRegexCheck(usernameExclusionPat)
- usernameMatches := usernameRegexState.Matches(data)
- if len(usernameMatches) == 0 {
- return nil, nil
- }
-
- passwordRegexState := common.PasswordRegexCheck(" \r\n") // Exclude spaces, carriage returns, and line feeds
- passwordMatches := passwordRegexState.Matches(data)
- if len(passwordMatches) == 0 {
- return nil, nil
- }
-
- for resAccountMatch := range uniqueAccountMatches {
- for _, resUsernameMatch := range usernameMatches {
- for _, resPasswordMatch := range passwordMatches {
- metPasswordRequirements := meetsSnowflakePasswordRequirements(resPasswordMatch)
- if !metPasswordRequirements {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Snowflake,
- Raw: []byte(resPasswordMatch),
- ExtraData: map[string]string{
- "account": resAccountMatch,
- "username": resUsernameMatch,
- },
- }
-
- if !verify {
- results = append(results, s1)
- continue
- }
-
- verified, err := verifyMatch(ctx, resAccountMatch, resUsernameMatch, resPasswordMatch)
- s1.SetVerificationError(err, resPasswordMatch)
- s1.Verified = verified
- results = append(results, s1)
- }
- }
- }
- return results, nil
-}
-
-// verifyMatch attempts to verify a Snowflake credential by making a login request.
-func verifyMatch(ctx context.Context, account, username, password string) (bool, error) {
- loginReq := loginRequest{}
- loginReq.Data.LoginName = username
- loginReq.Data.Password = password
- loginReq.Data.AccountName = account
-
- jsonData, err := json.Marshal(loginReq)
- if err != nil {
- return false, fmt.Errorf("failed to marshal login request: %w", err)
- }
-
- // Note: This endpoint is undocumented in Snowflake's public API documentation.
- url := fmt.Sprintf("https://%s.snowflakecomputing.com/session/v1/login-request", account)
- req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
- if err != nil {
- return false, fmt.Errorf("failed to create request: %w", err)
- }
-
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Accept", "application/json")
- req.Header.Set("X-Snowflake-Authorization-Token-Type", "BASIC")
-
- client := &http.Client{Timeout: timeout}
- resp, err := client.Do(req)
- if err != nil {
- return false, fmt.Errorf("failed to send request: %w", err)
- }
- defer resp.Body.Close()
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, fmt.Errorf("failed to read response body: %w", err)
- }
-
- if resp.StatusCode != http.StatusOK {
- return false, nil
- }
-
- var loginResp loginResponse
- if err := json.Unmarshal(body, &loginResp); err != nil {
- return false, fmt.Errorf("failed to parse response: %w", err)
- }
-
- return loginResp.Success, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Snowflake
-}
-
-func (s Scanner) Description() string {
- return "Snowflake is a cloud data platform that provides data warehousing, data lakes, data sharing, and data exchange capabilities. Snowflake credentials can be used to access and manipulate data stored in Snowflake."
-}
diff --git a/pkg/detectors/snowflake/snowflake_integration_test.go b/pkg/detectors/snowflake/snowflake_integration_test.go
deleted file mode 100644
index b51a6d2eeedc..000000000000
--- a/pkg/detectors/snowflake/snowflake_integration_test.go
+++ /dev/null
@@ -1,175 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package snowflake
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSnowflake_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- accountIdentifier := testSecrets.MustGetField("SNOWFLAKE_ACCOUNT")
- username := testSecrets.MustGetField("SNOWFLAKE_USERNAME")
- password := testSecrets.MustGetField("SNOWFLAKE_PASS")
- inactivePassword := testSecrets.MustGetField("SNOWFLAKE_PASS_INACTIVE")
-
- // Create a context with a past deadline to simulate DeadlineExceeded error
- pastTime := time.Now().Add(-time.Second) // Set the deadline in the past
- errorCtx, cancel := context.WithDeadline(context.Background(), pastTime)
- defer cancel()
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("snowflake: \n account=%s \n username=%s \n password=%s \n database=SNOWFLAKE", accountIdentifier, username, password)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Snowflake,
- Verified: true,
- ExtraData: map[string]string{
- "account": accountIdentifier,
- "username": username,
- },
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("snowflake: \n account=%s \n username=%s \n password=%s \n database=SNOWFLAKE", accountIdentifier, username, inactivePassword)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Snowflake,
- Verified: false,
- ExtraData: map[string]string{
- "account": accountIdentifier,
- "username": username,
- },
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, indeterminate error (timeout)",
- s: Scanner{},
- args: args{
- ctx: errorCtx,
- data: []byte(fmt.Sprintf("snowflake: \n account=%s \n username=%s \n password=%s \n database=SNOWFLAKE", accountIdentifier, username, password)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Snowflake,
- ExtraData: map[string]string{
- "account": accountIdentifier,
- "username": username,
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Snowflake.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- keysToCopy := []string{"account", "username"}
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
-
- got[i].ExtraData = newMap(got[i].ExtraData, keysToCopy)
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "primarySecret")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Snowflake.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func newMap(extraMap map[string]string, keysToCopy []string) map[string]string {
- newExtraDataMap := make(map[string]string)
- for _, key := range keysToCopy {
- if value, ok := extraMap[key]; ok {
- newExtraDataMap[key] = value
- }
- }
- return newExtraDataMap
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/snowflake/snowflake_test.go b/pkg/detectors/snowflake/snowflake_test.go
deleted file mode 100644
index c13437876c60..000000000000
--- a/pkg/detectors/snowflake/snowflake_test.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package snowflake
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/brianvoe/gofakeit/v7"
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestSnowflake_Pattern(t *testing.T) {
-
- validAccount := "tuacoip-zt74995"
- validPrivateLinkAccount := "tuacoip-zt74995.privatelink"
- validSingleCharacterAccount := "tuacoip-z"
- validUsername := gofakeit.Username()
- invalidUsername := "Spencer@5091.com" // special characters not allowed
-
- validPassword := common.GenerateRandomPassword(true, true, true, false, 10)
- invalidPassword := "!12" // invalid length
-
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want [][]string
- }{
- {
- name: "Snowflake Credentials",
- input: fmt.Sprintf("snowflake: \n account=%s \n username=%s \n password=%s \n database=SNOWFLAKE", validAccount, validUsername, validPassword),
- want: [][]string{
- {validAccount, validUsername, validPassword},
- },
- },
- {
- name: "Private Snowflake Credentials",
- input: fmt.Sprintf("snowflake: \n account=%s \n username=%s \n password=%s \n database=SNOWFLAKE", validPrivateLinkAccount, validUsername, validPassword),
- want: [][]string{
- {validPrivateLinkAccount, validUsername, validPassword},
- },
- },
- {
- name: "Snowflake Credentials - Single Character account",
- input: fmt.Sprintf("snowflake: \n account=%s \n username=%s \n password=%s \n database=SNOWFLAKE", validSingleCharacterAccount, validUsername, validPassword),
- want: [][]string{
- {validSingleCharacterAccount, validUsername, validPassword},
- },
- },
- {
- name: "Snowflake Credentials - Invalid Username & Password",
- input: fmt.Sprintf("snowflake: \n account=%s \n username=%s \n password=%s \n database=SNOWFLAKE", validAccount, invalidUsername, invalidPassword),
- want: [][]string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- detectorMatches := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(detectorMatches) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- resultsArray := make([][]string, len(results))
- for i, r := range results {
- resultsArray[i] = []string{r.ExtraData["account"], r.ExtraData["username"], string(r.Raw)}
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- actual[r.ExtraData["account"]] = struct{}{}
- actual[r.ExtraData["username"]] = struct{}{}
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- for _, value := range v {
- expected[value] = struct{}{}
- }
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/snykkey/snykkey.go b/pkg/detectors/snykkey/snykkey.go
deleted file mode 100644
index 2a017c5d70cb..000000000000
--- a/pkg/detectors/snykkey/snykkey.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package snykkey
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"snyk"}) + `\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\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{"snyk"}
-}
-
-// FromData will find and optionally verify SnykKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- tokens := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- tokens[match[1]] = struct{}{}
- }
-
- for token := range tokens {
- s1 := detectors.Result{
- DetectorType: s.Type(),
- Raw: []byte(token),
- }
-
- if verify {
- isVerified, extraData, verificationErr := s.doVerification(ctx, token)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, token)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) doVerification(ctx context.Context, token string) (bool, map[string]string, error) {
- client := s.client
- if client == nil {
- client = common.SaneHttpClient()
- }
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://snyk.io/api/v1/user/me", nil)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("token %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
- if res.StatusCode == http.StatusOK {
- userDetails := userDetailsResponse{}
- err := json.NewDecoder(res.Body).Decode(&userDetails)
- if err != nil {
- return true, nil, err
- } else if userDetails.Username == "" {
- return false, nil, fmt.Errorf("failed to decode JSON response")
- }
-
- extraData := map[string]string{
- "Username": userDetails.Username,
- "Email": userDetails.Email,
- }
-
- // Condense a list of organizations
- if len(userDetails.Organizations) > 0 {
- var orgs []string
- for _, org := range userDetails.Organizations {
- orgs = append(orgs, org.Name)
- }
- extraData["Organizations"] = strings.Join(orgs, ",")
- }
- return true, extraData, nil
- } else if res.StatusCode == http.StatusUnauthorized {
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- return false, nil, err
- }
-}
-
-// https://snyk.docs.apiary.io/#reference/users/my-user-details/get-my-details
-type userDetailsResponse struct {
- Username string `json:"username"`
- Email string `json:"email"`
- Organizations []organization `json:"orgs"`
-}
-
-type organization struct {
- Name string `json:"name"`
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SnykKey
-}
-
-func (s Scanner) Description() string {
- return "Snyk is a developer security platform that helps developers find and fix vulnerabilities in their code. Snyk API keys can be used to access and manage Snyk services."
-}
diff --git a/pkg/detectors/snykkey/snykkey_integration_test.go b/pkg/detectors/snykkey/snykkey_integration_test.go
deleted file mode 100644
index 651b298b03fd..000000000000
--- a/pkg/detectors/snykkey/snykkey_integration_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package snykkey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSnykKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SNYKKEY_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SNYKKEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a snykkey secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SnykKey,
- Verified: true,
- ExtraData: map[string]string{
- "Email": "rendyplayground@gmail.com",
- "Organizations": "rendyplayground",
- "Username": "rendyplayground",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a snykkey secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SnykKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SnykKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SnykKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/snykkey/snykkey_test.go b/pkg/detectors/snykkey/snykkey_test.go
deleted file mode 100644
index a28db8a104c1..000000000000
--- a/pkg/detectors/snykkey/snykkey_test.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package snykkey
-
-import (
- "context"
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
- "testing"
-)
-
-func TestSnyk_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: `set NODE_REQUIRED_VERSION=12.13.1
-set SNYK_API_TOKEN=885953dc-2469-443c-983d-5243d2d54116
-
-set PATH=%PATH%;C:\Program Files\nodejs\;C:\Program Files\Git\cmd`,
- want: []string{"885953dc-2469-443c-983d-5243d2d54116"},
- },
- // https://docs.snyk.io/snyk-api/get-a-projects-sbom-document-endpoint#how-to-generate-the-sbom-for-a-project
- // {
- // name: "curl example",
- // `curl --get \
- // -H "Authorization: token ccc9ae71-913f-46bd-9d23-03356323400a" \
- // --data-urlencode "version=2023-03-20" \
- // --data-urlencode "format=cyclonedx1.4%2Bjson" \
- // https://api.snyk.io/rest/orgs/1234/projects/1234/sbom`,
- // },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matches := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matches) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sonarcloud/sonarcloud.go b/pkg/detectors/sonarcloud/sonarcloud.go
deleted file mode 100644
index d74226daa81e..000000000000
--- a/pkg/detectors/sonarcloud/sonarcloud.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package sonarcloud
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sonar"}) + `(?:^|[^@])\b([0-9a-z]{40})\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{"sonar"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
-
- return defaultClient
-}
-
-// FromData will find and optionally verify SonarCloud secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueTokenMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokenMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueTokenMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SonarCloud,
- Raw: []byte(match),
- }
-
- if verify {
- isVerified, verificationErr := s.verifyMatch(ctx, s.getClient(), match)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-// verifyMatch attempts to validate a SonarCloud token.
-func (s Scanner) verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- url := "https://sonarcloud.io/api/authentication/validate"
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
- if err != nil {
- return false, fmt.Errorf("failed to create request: %w", err)
- }
-
- req.SetBasicAuth(token, "")
- res, err := client.Do(req)
- if err != nil {
- return false, fmt.Errorf("failed to perform request: %w", err)
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- // The SonarCloud API always returns 200 OK, even for invalid tokens,
- // with the validity indicated in the JSON body.
- if res.StatusCode != http.StatusOK {
- // Treat any non-200 status as a failed attempt to verify.
- return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
- }
-
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, fmt.Errorf("failed to read response body: %w", err)
- }
-
- var resp struct {
- Valid bool `json:"valid"`
- }
-
- if err := json.Unmarshal(bodyBytes, &resp); err != nil {
- return false, fmt.Errorf("invalid JSON: %w", err)
- }
-
- return resp.Valid, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SonarCloud
-}
-
-func (s Scanner) Description() string {
- return "SonarCloud is a cloud-based code quality and security service. SonarCloud tokens can be used to access project analysis and quality reports."
-}
diff --git a/pkg/detectors/sonarcloud/sonarcloud_integration_test.go b/pkg/detectors/sonarcloud/sonarcloud_integration_test.go
deleted file mode 100644
index fb9554885720..000000000000
--- a/pkg/detectors/sonarcloud/sonarcloud_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package sonarcloud
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSonarCloud_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SONARCLOUD")
- inactiveSecret := testSecrets.MustGetField("SONARCLOUD_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sonarcloud secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SonarCloud,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sonarcloud secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SonarCloud,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SonarCloud.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SonarCloud.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/sonarcloud/sonarcloud_test.go b/pkg/detectors/sonarcloud/sonarcloud_test.go
deleted file mode 100644
index 9167a100388c..000000000000
--- a/pkg/detectors/sonarcloud/sonarcloud_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package sonarcloud
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "hxrxxgxtcjxj7ta3dn33c5r5i2h0i6cqjv9kwkye"
- invalidPattern = "hxrxxgxt?jxj7ta3dn33c5r5i2h0i6cqjv9kwkye"
- keyword = "sonarcloud"
-)
-
-func TestSonarCloud_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword sonarcloud",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- {
- name: "invalid pattern - token directly preceded by @",
- input: fmt.Sprintf("%s token = '@%s'", keyword, validPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sourcegraph/sourcegraph.go b/pkg/detectors/sourcegraph/sourcegraph.go
deleted file mode 100644
index 93566d4537be..000000000000
--- a/pkg/detectors/sourcegraph/sourcegraph.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package sourcegraph
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\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.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"sgp_"}
-}
-
-// FromData will find and optionally verify Sourcegraph secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.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
- if client == nil {
- client = defaultClient
- }
- payload := strings.NewReader("{\"query\": \"query { currentUser { username } }\"}")
-
- req, err := http.NewRequestWithContext(ctx, "POST", "https://sourcegraph.com/.api/graphql", payload)
- req.Header.Add("Authorization", "token "+resMatch)
- req.Header.Add("Content-Type", "application/json")
-
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
-
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- } else if res.StatusCode == 401 {
- // The secret is determinately not verified (nothing to do)
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- s1.AnalysisInfo = map[string]string{"key": resMatch}
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Sourcegraph
-}
-
-func (s Scanner) Description() string {
- return "Sourcegraph is a code search and navigation engine. Sourcegraph tokens can be used to access and interact with Sourcegraph APIs."
-}
diff --git a/pkg/detectors/sourcegraph/sourcegraph_integration_test.go b/pkg/detectors/sourcegraph/sourcegraph_integration_test.go
deleted file mode 100644
index fb695ca4eceb..000000000000
--- a/pkg/detectors/sourcegraph/sourcegraph_integration_test.go
+++ /dev/null
@@ -1,253 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package sourcegraph
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSourcegraph_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- 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")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified v1",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraph secret %s within", secretV1)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sourcegraph,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, verified v1",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraph secret %s within", secretV1)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sourcegraph,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, verified v2",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraph secret %s within", secretV2)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sourcegraph,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, verified v3",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraph secret %s within", secretV3)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sourcegraph,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified v1",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraph secret %s within but not valid", inactiveSecretV1)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sourcegraph,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified v2",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraph secret %s within but not valid", inactiveSecretV2)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sourcegraph,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified v3",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraph secret %s within but not valid", inactiveSecretV3)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sourcegraph,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraph secret %s within", secretV1)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sourcegraph,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraph secret %s within", secretV1)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sourcegraph,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Sourcegraph.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- got[i].AnalysisInfo = nil
- }
- 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)
- }
- })
- }
-}
-
-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)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/sourcegraph/sourcegraph_test.go b/pkg/detectors/sourcegraph/sourcegraph_test.go
deleted file mode 100644
index 8e7bcf84b0fe..000000000000
--- a/pkg/detectors/sourcegraph/sourcegraph_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package sourcegraph
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "sgp_4a2aD05B07721CE0_EEBdEacB356CdC6Dc8CD317A4276e2cEE9cb118a"
- invalidPattern = "sgp_4a2aD0?B07721CE0_EEBdEacB356CdC6Dc8CD317A4276e2cEE9cb118a"
- keyword = "sourcegraph"
-)
-
-func TestSourcegraph_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword sourcegraph",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sourcegraphcody/sourcegraphcody.go b/pkg/detectors/sourcegraphcody/sourcegraphcody.go
deleted file mode 100644
index 37aa58b14732..000000000000
--- a/pkg/detectors/sourcegraphcody/sourcegraphcody.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package sourcegraphcody
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(slk_[a-f0-9]{64})\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{"slk_"}
-}
-
-// FromData will find and optionally verify Sourcegraphcody secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SourcegraphCody,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- req, err := http.NewRequestWithContext(ctx, "GET", "https://cody-gateway.sourcegraph.com/v1/limits", nil)
- req.Header.Add("Authorization", "Bearer "+resMatch)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- } else if res.StatusCode == 403 || res.StatusCode == 401 {
- // 403 when the gateway is not configured to accept the token,
- // 401 when the token is invalid.
- // The secret is determinately not verified (nothing to do)
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SourcegraphCody
-}
-
-func (s Scanner) Description() string {
- return "Sourcegraph Cody is a tool for integrating code intelligence into your development workflow. The detected key is used for accessing Sourcegraph Cody services."
-}
diff --git a/pkg/detectors/sourcegraphcody/sourcegraphcody_integration_test.go b/pkg/detectors/sourcegraphcody/sourcegraphcody_integration_test.go
deleted file mode 100644
index 824beac3c4dd..000000000000
--- a/pkg/detectors/sourcegraphcody/sourcegraphcody_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package sourcegraphcody
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSourcegraphcody_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SOURCEGRAPHCODY")
- inactiveSecret := testSecrets.MustGetField("SOURCEGRAPHCODY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SourcegraphCody,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SourcegraphCody,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SourcegraphCody,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SourcegraphCody,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Sourcegraphcody.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Sourcegraphcody.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/sourcegraphcody/sourcegraphcody_test.go b/pkg/detectors/sourcegraphcody/sourcegraphcody_test.go
deleted file mode 100644
index cac9cd42fae0..000000000000
--- a/pkg/detectors/sourcegraphcody/sourcegraphcody_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package sourcegraphcody
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "slk_3c6669a93b14fbd6061e00fb40876b327826df4fe393e760d3109de08165e793"
- invalidPattern = "slk_?c6669a93b14fbd6061e00fb40876b327826df4fe393e760d3109de08165e793"
- keyword = "sourcegraphcody"
-)
-
-func TestSourcegraphcody_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword sourcegraphcody",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sparkpost/sparkpost.go b/pkg/detectors/sparkpost/sparkpost.go
deleted file mode 100644
index e73b0e0fc4b5..000000000000
--- a/pkg/detectors/sparkpost/sparkpost.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package sparkpost
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b([a-zA-Z0-9]{40})\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{"sparkpost"}
-}
-
-// FromData will find and optionally verify Sparkpost secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Sparkpost,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.sparkpost.com/api/v1/account", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Sparkpost
-}
-
-func (s Scanner) Description() string {
- return "Sparkpost is an email delivery service. Sparkpost API keys can be used to send and manage emails through Sparkpost's API."
-}
diff --git a/pkg/detectors/sparkpost/sparkpost_integration_test.go b/pkg/detectors/sparkpost/sparkpost_integration_test.go
deleted file mode 100644
index 6711fe6396a0..000000000000
--- a/pkg/detectors/sparkpost/sparkpost_integration_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package sparkpost
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSparkpost_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SPARKPOST_API_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SPARKPOST_API_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sparkpost secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sparkpost,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sparkpost secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sparkpost,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
-
- if (err != nil) != tt.wantErr {
- t.Errorf("Sparkpost.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- }
-
- if diff := pretty.Compare(got, tt.want); diff != "" {
-
- t.Errorf("Sparkpost.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/sparkpost/sparkpost_test.go b/pkg/detectors/sparkpost/sparkpost_test.go
deleted file mode 100644
index 468120100f16..000000000000
--- a/pkg/detectors/sparkpost/sparkpost_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package sparkpost
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "VlsweyCt4GRglwZ8G3xVo5lY8Z4Wo9cLxyHFWh9V"
- invalidPattern = "VlsweyCt4GRglwZ8G3xV?5lY8Z4Wo9cLxyHFWh9V"
- keyword = "sparkpost"
-)
-
-func TestSparkpost_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword sparkpost",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/speechtextai/speechtextai.go b/pkg/detectors/speechtextai/speechtextai.go
deleted file mode 100644
index bdb0c8dfaef6..000000000000
--- a/pkg/detectors/speechtextai/speechtextai.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package speechtextai
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"speechtext"}) + common.BuildRegex(common.HexPattern, "", 32))
-)
-
-// 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{"speechtext"}
-}
-
-// FromData will find and optionally verify SpeechTextAI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SpeechTextAI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("https://api.speechtext.ai/recognize?key=%s&language=en-US&punctuation=true&format=m4a", resMatch), nil)
- req.Header.Add("Content-Type", "application/octet-stream")
-
- if err != nil {
- continue
- }
-
- res, err := client.Do(req)
-
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SpeechTextAI
-}
-
-func (s Scanner) Description() string {
- return "SpeechTextAI is a service that provides speech-to-text conversion. The API key can be used to access and utilize the speech-to-text services offered by SpeechTextAI."
-}
diff --git a/pkg/detectors/speechtextai/speechtextai_integration_test.go b/pkg/detectors/speechtextai/speechtextai_integration_test.go
deleted file mode 100644
index f3184a5fd53d..000000000000
--- a/pkg/detectors/speechtextai/speechtextai_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package speechtextai
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSpeechTextAI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SPEECHTEXTAI")
- inactiveSecret := testSecrets.MustGetField("SPEECHTEXTAI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a speechtext secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SpeechTextAI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a speechtext secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SpeechTextAI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SpeechTextAI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SpeechTextAI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/speechtextai/speechtextai_test.go b/pkg/detectors/speechtextai/speechtextai_test.go
deleted file mode 100644
index 6c1ef9183322..000000000000
--- a/pkg/detectors/speechtextai/speechtextai_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package speechtextai
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "51bb70d3ab4f9e0ecd17ec0a6f8ed289"
- invalidPattern = "G1bb70d3ab4f9e0ecd17ec0a6f8ed289"
- keyword = "speechtextai"
-)
-
-func TestSpeechTextAI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword speechtextai",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken.go b/pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken.go
deleted file mode 100644
index 0c9059792063..000000000000
--- a/pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package splunkobservabilitytoken
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"splunk"}) + `\b([a-z0-9A-Z]{22})\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{"splunk"}
-}
-
-// FromData will find and optionally verify SplunkAccessToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SplunkOberservabilityToken,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.us1.signalfx.com/v2/dashboard", nil)
- if err != nil {
- continue
- }
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("X-Sf-Token", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SplunkOberservabilityToken
-}
-
-func (s Scanner) Description() string {
- return "Splunk Observability Tokens are used to access and interact with Splunk's observability suite, allowing for monitoring and analyzing application performance and infrastructure."
-}
diff --git a/pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken_integration_test.go b/pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken_integration_test.go
deleted file mode 100644
index 49de9e00aec2..000000000000
--- a/pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package splunkobservabilitytoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSplunkObservabilityToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SPLUNK_ACCESS_TOKEN") // TODO: rename
- inactiveSecret := testSecrets.MustGetField("SPLUNK_ACCESS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a splunk secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SplunkOberservabilityToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a splunk secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SplunkOberservabilityToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SplunkObservabilityToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SplunkObservabilityToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken_test.go b/pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken_test.go
deleted file mode 100644
index 9cf7298d16be..000000000000
--- a/pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package splunkobservabilitytoken
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "OlKU6qOr3U4AlCziwvFCY2"
- invalidPattern = "OlKU6qOr3U4?lCziwvFCY2"
- keyword = "splunkobservabilitytoken"
-)
-
-func TestSplunkObservabilityToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword splunkobservabilitytoken",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/spoonacular/spoonacular.go b/pkg/detectors/spoonacular/spoonacular.go
deleted file mode 100644
index edb8a5b13b29..000000000000
--- a/pkg/detectors/spoonacular/spoonacular.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package spoonacular
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"spoonacular"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"spoonacular"}
-}
-
-// FromData will find and optionally verify Spoonacular secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Spoonacular,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.spoonacular.com/recipes/random?apiKey=%s", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Spoonacular
-}
-
-func (s Scanner) Description() string {
- return "Spoonacular provides a food and recipe API. Spoonacular API keys can be used to access and manage food and recipe data."
-}
diff --git a/pkg/detectors/spoonacular/spoonacular_integration_test.go b/pkg/detectors/spoonacular/spoonacular_integration_test.go
deleted file mode 100644
index 91ba6069b8f6..000000000000
--- a/pkg/detectors/spoonacular/spoonacular_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package spoonacular
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSpoonacular_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SPOONACULAR")
- inactiveSecret := testSecrets.MustGetField("SPOONACULAR_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a spoonacular secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Spoonacular,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a spoonacular secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Spoonacular,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Spoonacular.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Spoonacular.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/spoonacular/spoonacular_test.go b/pkg/detectors/spoonacular/spoonacular_test.go
deleted file mode 100644
index d8d5e323bee1..000000000000
--- a/pkg/detectors/spoonacular/spoonacular_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package spoonacular
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "xyclb6zdiq9hyexe74ah4lvp3ea02sbv"
- invalidPattern = "xyclb6zdiq9hyexe?4ah4lvp3ea02sbv"
- keyword = "spoonacular"
-)
-
-func TestSpoonacular_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword spoonacular",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sportsmonk/sportsmonk.go b/pkg/detectors/sportsmonk/sportsmonk.go
deleted file mode 100644
index ebc1180c8639..000000000000
--- a/pkg/detectors/sportsmonk/sportsmonk.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package sportsmonk
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sportsmonk"}) + `\b([0-9a-zA-Z]{60})\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{"sportsmonk"}
-}
-
-// FromData will find and optionally verify Sportsmonk secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Sportsmonk,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://soccer.sportmonks.com/api/v2.0/leagues?api_token="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Sportsmonk
-}
-
-func (s Scanner) Description() string {
- return "Sportsmonk is a sports data provider offering various API endpoints to access sports data. Sportsmonk API keys can be used to query and retrieve this data."
-}
diff --git a/pkg/detectors/sportsmonk/sportsmonk_integration_test.go b/pkg/detectors/sportsmonk/sportsmonk_integration_test.go
deleted file mode 100644
index 399df68b6d7b..000000000000
--- a/pkg/detectors/sportsmonk/sportsmonk_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package sportsmonk
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSportsmonk_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SPORTSMONK")
- inactiveSecret := testSecrets.MustGetField("SPORTSMONK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sportsmonk secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sportsmonk,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sportsmonk secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sportsmonk,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Sportsmonk.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Sportsmonk.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/sportsmonk/sportsmonk_test.go b/pkg/detectors/sportsmonk/sportsmonk_test.go
deleted file mode 100644
index 4bf2390e4659..000000000000
--- a/pkg/detectors/sportsmonk/sportsmonk_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package sportsmonk
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "GxSYzYakznVR2ZSu7cdIGjj2g3m4VD7vkhToot3WKwEJlgibw4a6akEeV55V"
- invalidPattern = "GxSYzYakznVR2ZSu?cdIGjj2g3m4VD7vkhToot3WKwEJlgibw4a6akEeV55V"
- keyword = "sportsmonk"
-)
-
-func TestSportsmonk_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword sportsmonk",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/spotifykey/spotifykey.go b/pkg/detectors/spotifykey/spotifykey.go
deleted file mode 100644
index 3b8bf03dc25b..000000000000
--- a/pkg/detectors/spotifykey/spotifykey.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package spotifykey
-
-import (
- "context"
- "golang.org/x/oauth2"
-
- regexp "github.com/wasilibs/go-re2"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "golang.org/x/oauth2/clientcredentials"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"key", "secret"}) + `\b([A-Za-z0-9]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"id"}) + `\b([A-Za-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"spotify"}
-}
-
-// FromData will find and optionally verify SpotifyKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- ctx = context.WithValue(ctx, oauth2.HTTPClient, common.SaneHttpClient())
-
- dataStr := string(data)
-
- matches := secretPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, idMatch := range idMatches {
- idresMatch := strings.TrimSpace(idMatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SpotifyKey,
- Raw: []byte(resMatch),
- }
-
- if verify {
- config := &clientcredentials.Config{
- ClientID: idresMatch,
- ClientSecret: resMatch,
- TokenURL: "https://accounts.spotify.com/api/token",
- }
- token, err := config.Token(ctx)
- if err == nil {
- if token.Type() == "Bearer" {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SpotifyKey
-}
-
-func (s Scanner) Description() string {
- return "Spotify API keys can be used to access and modify data within Spotify's services."
-}
diff --git a/pkg/detectors/spotifykey/spotifykey_integration_test.go b/pkg/detectors/spotifykey/spotifykey_integration_test.go
deleted file mode 100644
index a38741bfa528..000000000000
--- a/pkg/detectors/spotifykey/spotifykey_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package spotifykey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSpotifyKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- clientID := testSecrets.MustGetField("SPOTIFY_CLIENTID")
- clientSecret := testSecrets.MustGetField("SPOTIFY_CLIENT_SECRET")
- inactiveClientSecret := testSecrets.MustGetField("SPOTIFY_CLIENT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a spotifyKey secret %s within spotifyId %s", clientSecret, clientID)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SpotifyKey,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a spotifyKey secret %s within spotifyId %s but not valid", inactiveClientSecret, clientID)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SpotifyKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SpotifyKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SpotifyKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/spotifykey/spotifykey_test.go b/pkg/detectors/spotifykey/spotifykey_test.go
deleted file mode 100644
index 8999a08ba02f..000000000000
--- a/pkg/detectors/spotifykey/spotifykey_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package spotifykey
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validSecret = "MJsy6euxw0GKljGYZLZGnn0PvKuZB0V8"
- invalidSecret = "MJsy6euxw0GKljGY?LZGnn0PvKuZB0V8"
- validId = "JykXADk7r8ZegkS0emhtpFSWNqGVAIgm"
- invalidId = "JykXADk7r8Z?gkS0emhtpFSWNqGVAIgm"
- keyword = "spotifykey"
-)
-
-func TestSpotifyKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword spotifykey",
- input: fmt.Sprintf("%s key - '%s'\n%s id - '%s'\n", keyword, validSecret, keyword, validId),
- want: []string{validSecret, validId},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s key - '%s'\n%s id - '%s'\n", keyword, invalidSecret, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sqlserver/sqlserver.go b/pkg/detectors/sqlserver/sqlserver.go
deleted file mode 100644
index 2837107529bd..000000000000
--- a/pkg/detectors/sqlserver/sqlserver.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package sqlserver
-
-import (
- "context"
- "database/sql"
- "fmt"
- "net"
- "strconv"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- mssql "github.com/microsoft/go-mssqldb"
- "github.com/microsoft/go-mssqldb/msdsn"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- // SQLServer connection string is a semicolon delimited set of case-insensitive parameters which may go in any order.
- pattern = regexp.MustCompile("(?:\n|`|'|\"| )?((?:[A-Za-z0-9_ ]+=[^;$'`\"$]+;?){3,})(?:'|`|\"|\r\n|\n)?")
-)
-
-// 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{"sql", "database", "Data Source", "Server=", "Network address="}
-}
-
-// FromData will find and optionally verify SQL Server credentials in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- matches := pattern.FindAllStringSubmatch(string(data), -1)
- for _, match := range matches {
- paramsUnsafe, err := msdsn.Parse(match[1])
- if err != nil {
- continue
- }
-
- if paramsUnsafe.Password == "" {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SQLServer,
- Raw: []byte(paramsUnsafe.Password),
- RawV2: []byte(paramsUnsafe.URL().String()),
- Redacted: detectors.RedactURL(*paramsUnsafe.URL()),
- }
-
- if verify {
- isVerified, err := ping(ctx, paramsUnsafe)
-
- s1.Verified = isVerified
- mssqlErr, isMssqlErr := err.(mssql.Error)
- if isMssqlErr {
- if mssqlErr.Number == 18456 {
- // Login failed
- // Number taken from https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors?view=sql-server-ver16
- // Nothing to do; determinate failure to verify
- } else {
- // If it is a MSSQL error, format the error with error number and message
- s1.SetVerificationError(fmt.Errorf("SQL Server error %d: %s", mssqlErr.Number, mssqlErr.Message), paramsUnsafe.Password)
- }
- } else if err != nil {
- // If it is an error but not of MSSQL error type, just set error as verification error
- s1.SetVerificationError(err, paramsUnsafe.Password)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-var ping = func(ctx context.Context, config msdsn.Config) (bool, error) {
- // TCP connectivity check to prevent indefinite hangs
- address := net.JoinHostPort(config.Host, strconv.Itoa(int(config.Port)))
-
- dialer := &net.Dialer{
- Timeout: 3 * time.Second,
- }
-
- tcpConn, err := dialer.DialContext(ctx, "tcp", address) // respects context timeout
- if err != nil {
- return false, err
- }
- defer tcpConn.Close()
-
- cleanConfig := msdsn.Config{}
- cleanConfig.Host = config.Host
- cleanConfig.Port = config.Port
- cleanConfig.User = config.User
- cleanConfig.Password = config.Password
- cleanConfig.Database = config.Database
- cleanConfig.DisableRetry = true
- cleanConfig.Encryption = config.Encryption
- cleanConfig.TLSConfig = config.TLSConfig
- cleanConfig.Instance = config.Instance
- cleanConfig.DialTimeout = time.Second * 3
- cleanConfig.ConnTimeout = time.Second * 3
-
- url := cleanConfig.URL()
- query := url.Query()
- url.RawQuery = query.Encode()
-
- conn, err := sql.Open("mssql", url.String())
- if err != nil {
- return false, err
- }
- defer func() {
- _ = conn.Close()
- }()
-
- err = conn.PingContext(ctx) // this doesn't seem to respect the context timeout
- if err != nil {
- return false, err
- }
-
- return true, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SQLServer
-}
-
-func (s Scanner) Description() string {
- return "SQL Server is a relational database management system developed by Microsoft. SQL Server credentials can be used to access and manage databases."
-}
diff --git a/pkg/detectors/sqlserver/sqlserver_integration_test.go b/pkg/detectors/sqlserver/sqlserver_integration_test.go
deleted file mode 100644
index dacc62b2cf50..000000000000
--- a/pkg/detectors/sqlserver/sqlserver_integration_test.go
+++ /dev/null
@@ -1,377 +0,0 @@
-//go:build detectors && integration
-// +build detectors,integration
-
-package sqlserver
-
-import (
- "context"
- "errors"
- "fmt"
- "net/url"
- "testing"
-
- "github.com/brianvoe/gofakeit/v7"
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/microsoft/go-mssqldb/msdsn"
- "github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/modules/mssql"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSQLServerIntegration_FromChunk(t *testing.T) {
- ctx := context.Background()
-
- password := gofakeit.Password(true, true, true, false, false, 10)
-
- container, err := mssql.RunContainer(
- ctx,
- testcontainers.WithImage("mcr.microsoft.com/azure-sql-edge"),
- mssql.WithAcceptEULA(),
- mssql.WithPassword(password))
- if err != nil {
- t.Fatalf("could not start container: %v", err)
- }
-
- defer container.Terminate(ctx)
-
- port, err := container.MappedPort(ctx, "1433")
- if err != nil {
- t.Fatalf("could get mapped port: %v", err)
- }
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("Server=localhost;Port=%s;Initial Catalog=master;User ID=sa;Password=%s;Persist Security Info=true;MultipleActiveResultSets=true;",
- port.Port(),
- password)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SQLServer,
- Raw: []byte(password),
- RawV2: []byte(urlEncode(fmt.Sprintf("sqlserver://sa:%s@localhost:%s?database=master&dial+timeout=15&disableretry=false",
- password,
- port.Port()))),
- Redacted: fmt.Sprintf("sqlserver://sa:********@localhost:%s?database=master&dial+timeout=15&disableretry=false",
- port.Port()),
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("Server=localhost;Port=%s;User ID=sa;Password=123", port.Port())),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SQLServer,
- Raw: []byte("123"),
- RawV2: []byte(fmt.Sprintf("sqlserver://sa:123@localhost:%s?dial+timeout=15&disableretry=false",
- port.Port())),
- Redacted: fmt.Sprintf("sqlserver://sa:********@localhost:%s?dial+timeout=15&disableretry=false",
- port.Port()),
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found, in XML, missing password param (pwd is not valid)",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(` `),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- {
- name: "found, verified, in XML",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf(` `,
- port.Port(),
- password)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SQLServer,
- Redacted: fmt.Sprintf("sqlserver://sa:********@localhost:%s?database=master&dial+timeout=15&disableretry=false",
- port.Port()),
- Raw: []byte(password),
- RawV2: []byte(urlEncode(fmt.Sprintf("sqlserver://sa:%s@localhost:%s?database=master&dial+timeout=15&disableretry=false",
- password,
- port.Port()))),
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unreachable host",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("Server=unreachablehost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true;"),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SQLServer,
- Raw: []byte("P@ssw0rd!"),
- RawV2: []byte("sqlserver://sa:P%40ssw0rd%21@unreachablehost?database=master&dial+timeout=15&disableretry=false"),
- Redacted: "sqlserver://sa:********@unreachablehost?database=master&dial+timeout=15&disableretry=false",
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SQLServer.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("SQLServer.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func TestSQLServer_FromChunk(t *testing.T) {
- secret := "Server=localhost;Initial Catalog=Demo;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true;"
- inactiveSecret := "Server=localhost;User ID=sa;Password=123"
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- mockFunc func()
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sqlserver secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SQLServer,
- Redacted: "sqlserver://sa:********@localhost?database=Demo&dial+timeout=15&disableretry=false",
- Verified: true,
- },
- },
- wantErr: false,
- mockFunc: func() {
- ping = func(config msdsn.Config) (bool, error) {
- return true, nil
- }
- },
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sqlserver secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SQLServer,
- Redacted: "sqlserver://sa:********@localhost?dial+timeout=15&disableretry=false",
- Verified: false,
- },
- },
- wantErr: false,
- mockFunc: func() {
- ping = func(config msdsn.Config) (bool, error) {
- return false, nil
- }
- },
- },
- {
- name: "not found, in XML, missing password param (pwd is not valid)",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(` `),
- verify: true,
- },
- want: nil,
- wantErr: false,
- mockFunc: func() {
- ping = func(config msdsn.Config) (bool, error) {
- return true, nil
- }
- },
- },
- {
- name: "found, verified, in XML",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(` `),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SQLServer,
- Redacted: "sqlserver://username:********@server_name?database=testdb&dial+timeout=15&disableretry=false&encrypt=true",
- Verified: true,
- },
- },
- wantErr: false,
- mockFunc: func() {
- ping = func(config msdsn.Config) (bool, error) {
- if config.Host != "server_name" {
- return false, errors.New("invalid host")
- }
-
- if config.User != "username" {
- return false, errors.New("invalid database")
- }
-
- if config.Password != "badpassword" {
- return false, errors.New("invalid password")
- }
-
- if config.Database != "testdb" {
- return false, errors.New("invalid database")
- }
-
- return true, nil
- }
- },
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- mockFunc: func() {},
- },
- }
-
- // preserve the original function
- originalPing := ping
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- tt.mockFunc()
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SQLServer.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- ignoreOpts := []cmp.Option{
- cmpopts.IgnoreFields(detectors.Result{}, "RawV2"),
- cmpopts.IgnoreUnexported(detectors.Result{}),
- }
- if diff := cmp.Diff(tt.want, got, ignoreOpts...); diff != "" {
- t.Errorf("SQLServer.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- for _, g := range got {
- t.Error(g.Redacted)
- }
- }
- })
- }
-
- ping = originalPing
-}
-
-func urlEncode(s string) string {
- parsed, _ := url.Parse(s)
- return parsed.String()
-}
-
-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)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/sqlserver/sqlserver_test.go b/pkg/detectors/sqlserver/sqlserver_test.go
deleted file mode 100644
index fc5748b716b3..000000000000
--- a/pkg/detectors/sqlserver/sqlserver_test.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package sqlserver
-
-import (
- "testing"
-)
-
-func TestSQLServer_Pattern(t *testing.T) {
- if !pattern.Match([]byte(`builder.Services.AddDbContext(optionsBuilder => optionsBuilder.UseSqlServer("Server=localhost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true;"));`)) {
- t.Errorf("SQLServer.pattern: did not find connection string from Program.cs")
- }
- if !pattern.Match([]byte(`{"ConnectionStrings": {"Demo": "Server=localhost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true;"}}`)) {
- t.Errorf("SQLServer.pattern: did not find connection string from appsettings.json")
- }
- if !pattern.Match([]byte(`CONNECTION_STRING: Server=localhost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true`)) {
- t.Errorf("SQLServer.pattern: did not find connection string from .env")
- }
- if !pattern.Match([]byte(` `)) {
- t.Errorf("SQLServer.pattern: did not find connection string in xml format")
- }
-}
diff --git a/pkg/detectors/square/square.go b/pkg/detectors/square/square.go
deleted file mode 100644
index 94052a7f2dbf..000000000000
--- a/pkg/detectors/square/square.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package square
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- // more context to be added if this is too generic
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"square"}) + `(EAAA[a-zA-Z0-9\-\+\=]{60})`)
-)
-
-// 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{"EAAA"}
-}
-
-// FromData will find and optionally verify Square secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- // Surprisingly there are still a lot of false positives! So, also doing substring check for square.
- if !strings.Contains(strings.ToLower(dataStr), "square") {
- return
- }
-
- secMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
- for _, secMatch := range secMatches {
- resMatch := strings.TrimSpace(secMatch[1])
-
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_Square,
- Raw: []byte(resMatch),
- }
- result.ExtraData = map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/square/",
- }
-
- if verify {
- // there are a few endpoints we can check, but templates seems the least sensitive.
- // 403 will be issued if the scope is wrong but the key is correct
- baseURL := "https://connect.squareupsandbox.com/v2/merchants"
-
- client := common.SaneHttpClient()
-
- // test `merchants` scope - its commonly allowed and low sensitivity
- req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- req.Header.Add("Content-Type", "application/json")
- // unclear if this version needs to be set or matters, seems to work without, but docs want it
- // req.Header.Add("Square-Version", "2020-08-12")
- res, err := client.Do(req)
- if err == nil {
- res.Body.Close() // The request body is unused.
-
- // 200 means good key and has `merchants` scope - default allowed by square
- // 401 is bad key
- if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusForbidden {
- result.Verified = true
- }
- }
- result.AnalysisInfo = map[string]string{"key": resMatch}
- }
-
- results = append(results, result)
- }
-
- return
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Square
-}
-
-func (s Scanner) Description() string {
- return "Square is a financial services and mobile payment company. Square API keys can be used to access and manage payments, transactions, and other financial data."
-}
diff --git a/pkg/detectors/square/square_integration_test.go b/pkg/detectors/square/square_integration_test.go
deleted file mode 100644
index d6b7793d40c7..000000000000
--- a/pkg/detectors/square/square_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package square
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSquare_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SQUARE_SECRET")
- secretInactive := testSecrets.MustGetField("SQUARE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a square secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Square,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unvierifed",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a square secret %s within", secretInactive)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Square,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Square.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- got[i].AnalysisInfo = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Square.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/square/square_test.go b/pkg/detectors/square/square_test.go
deleted file mode 100644
index dadda930f3ea..000000000000
--- a/pkg/detectors/square/square_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package square
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "EAAAYmTgYL4kQ-65xyZ4zANgipYVJQFTrb=roK23I=iFebzL4rjYbUg10N6o1l--"
- invalidPattern = "EAAAYmTgYL4kQ-65xyZ4zANgipYVJQFT?b=roK23I=iFebzL4rjYbUg10N6o1l--"
- keyword = "square"
-)
-
-func TestSquare_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword square",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/squareapp/squareapp.go b/pkg/detectors/squareapp/squareapp.go
deleted file mode 100644
index 5f214d1efde7..000000000000
--- a/pkg/detectors/squareapp/squareapp.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package squareapp
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
- /*
- The sandbox id and secret has word `sandbox-` as prefix
- possibly always `sq0csp` for secret and `sq0idb` for app
- */
- keyPat = regexp.MustCompile(`(?:sandbox-)?sq0i[a-z]{2}-[0-9A-Za-z_-]{22,43}`)
- secPat = regexp.MustCompile(`(?:sandbox-)?sq0c[a-z]{2}-[0-9A-Za-z_-]{40,50}`)
-
- // api endpoints
- sandboxEndpoint = "https://connect.squareupsandbox.com/oauth2/revoke"
- prodEndpoint = "https://connect.squareup.com/oauth2/revoke"
-)
-
-// 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{"sq0i"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SquareApp
-}
-
-func (s Scanner) Description() string {
- return "Square is a financial services and mobile payment company. Square credentials can be used to access and manage payment processing and other financial services."
-}
-
-// FromData will find and optionally verify SquareApp secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueIDMatches, uniqueSecretMatches = make(map[string]struct{}), make(map[string]struct{})
-
- for _, match := range keyPat.FindAllString(dataStr, -1) {
- uniqueIDMatches[match] = struct{}{}
- }
-
- for _, match := range secPat.FindAllString(dataStr, -1) {
- uniqueSecretMatches[match] = struct{}{}
- }
-
- for id := range uniqueIDMatches {
- for secret := range uniqueSecretMatches {
- // if both are not from same env, continue
- if !hasSamePrefix(id, secret) {
- continue
- }
-
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_SquareApp,
- Raw: []byte(id),
- Redacted: id,
- ExtraData: map[string]string{},
- }
-
- var isVerified bool
- var verificationErr error
-
- // verify against sandbox endpoint
- if verify && isSandbox(id) {
- isVerified, verificationErr = verifySquareApp(ctx, client, sandboxEndpoint, id, secret)
- result.ExtraData["Env"] = "Sandbox"
- }
-
- // verify against prod endpoint
- if verify && !isSandbox(id) {
- isVerified, verificationErr = verifySquareApp(ctx, client, prodEndpoint, id, secret)
- result.ExtraData["Env"] = "Production"
- }
-
- result.Verified = isVerified
- result.SetVerificationError(verificationErr)
-
- results = append(results, result)
-
- // once a secret is verified with id, remove it from the list
- if isVerified {
- delete(uniqueSecretMatches, secret)
- }
- }
- }
-
- return results, nil
-}
-
-func verifySquareApp(ctx context.Context, client *http.Client, endpoint, id, secret string) (bool, error) {
- reqData, err := json.Marshal(map[string]string{
- "client_id": id,
- "access_token": "fakeTruffleHogAccessTokenForVerification",
- })
- if err != nil {
- return false, err
- }
-
- req, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(reqData))
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Client %s", secret))
- req.Header.Add("Content-Type", "application/json")
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusNotFound:
- return true, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-func hasSamePrefix(id, secret string) bool {
- idHasPrefix := strings.HasPrefix(id, "sandbox-")
- secretHasPrefix := strings.HasPrefix(secret, "sandbox-")
-
- return idHasPrefix == secretHasPrefix
-}
-
-// isSandbox check if provided key(id or secret) is of sandbox env
-func isSandbox(key string) bool {
- return strings.HasPrefix(key, "sandbox-")
-}
diff --git a/pkg/detectors/squareapp/squareapp_integration_test.go b/pkg/detectors/squareapp/squareapp_integration_test.go
deleted file mode 100644
index fdec17a3f569..000000000000
--- a/pkg/detectors/squareapp/squareapp_integration_test.go
+++ /dev/null
@@ -1,119 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package squareapp
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSquareApp_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- id := testSecrets.MustGetField("SQUAREAPP_ID")
- secret := testSecrets.MustGetField("SQUAREAPP_SECRET")
- secretInactive := testSecrets.MustGetField("SQUAREAPP_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a squareapp secret %s within awsId %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SquareApp,
- Verified: true,
- Redacted: id,
- ExtraData: map[string]string{"Env": "Sandbox"},
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified - detected but not added in result due to mismatch of env",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a squareapp secret %s within awsId %s", secretInactive, id)),
- verify: true,
- },
- want: []detectors.Result{},
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SquareApp.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SquareApp.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/squareapp/squareapp_test.go b/pkg/detectors/squareapp/squareapp_test.go
deleted file mode 100644
index f39ada19a4aa..000000000000
--- a/pkg/detectors/squareapp/squareapp_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package squareapp
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "sq0ige-a9khwVJOSwlzBvX0wp4j8t90s2d"
- invalidKey = "YJsq0ige-a9kh?VJOSwlzBvX0wp4j8t90s2d"
- validSec = "4sSPeeM_jk0VZiTFZJqEwzvXHjcCd6fsq0cvn-4pvyBIQ1OvY6dOv4X2AK5r6UJaFf0Xkp5NjV6lGhtbM"
- invalidSec = "4sSPeeM_jk0VZiTFZJqEwz?XHjcCd6fsq0cvn-4pvyBIQ1OvY6dOv4X2AK5r6UJaFf0Xkp5NjV6lGhtbM"
-
- // sandbox
- validSandboxKey = "sandbox-sq0idb-hFAKEQrhLGgFAKELZEDgpo"
- validSandboxSecret = "sandbox-sq0csb-o6cs8xFAKExEgIDGbzn2hFAKEZPbzhe713Q-FAKEfbY"
- keyword = "squareapp"
-)
-
-func TestSquareApp_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword squareapp",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validSec),
- want: []string{validKey},
- },
- {
- name: "valid sandbox pattern - with keyword squareapp",
- input: fmt.Sprintf("token - '%s'\n secret - '%s'\n", validSandboxKey, validSandboxSecret),
- want: []string{validSandboxKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidSec),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/squarespace/squarespace.go b/pkg/detectors/squarespace/squarespace.go
deleted file mode 100644
index 1398fb30e9fa..000000000000
--- a/pkg/detectors/squarespace/squarespace.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package squarespace
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"squarespace"}) + `\b([0-9Aa-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\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{"squarespace"}
-}
-
-// FromData will find and optionally verify Squarespace secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Squarespace,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.squarespace.com/1.0/profiles?sortField=email&sortDirection=asc&filter=isCustomer,true;hasAccount,true", nil)
- if err != nil {
- continue
- }
- req.Header.Add("User-Agent", "YOUR_CUSTOM_APP_DESCRIPTION")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Squarespace
-}
-
-func (s Scanner) Description() string {
- return "Squarespace is a website building and hosting service. Squarespace API keys can be used to manage and modify website content and configuration."
-}
diff --git a/pkg/detectors/squarespace/squarespace_integration_test.go b/pkg/detectors/squarespace/squarespace_integration_test.go
deleted file mode 100644
index 80308cb71cf3..000000000000
--- a/pkg/detectors/squarespace/squarespace_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package squarespace
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSquarespace_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SQUARESPACE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SQUARESPACE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a squarespace secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Squarespace,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a squarespace secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Squarespace,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Squarespace.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Squarespace.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/squarespace/squarespace_test.go b/pkg/detectors/squarespace/squarespace_test.go
deleted file mode 100644
index ff66e179ae1b..000000000000
--- a/pkg/detectors/squarespace/squarespace_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package squarespace
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "9308f78b-8315-a8a6-9c9c-a4b41488508f"
- invalidPattern = "9308f78b?8315-a8a6-9c9c-a4b41488508f"
- keyword = "squarespace"
-)
-
-func TestSquarespace_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword squarespace",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/squareup/squareup.go b/pkg/detectors/squareup/squareup.go
deleted file mode 100644
index bfa2a5ac4e20..000000000000
--- a/pkg/detectors/squareup/squareup.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package squareup
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(sq0idp-[0-9A-Za-z]{22})\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{"sq0idp-"}
-}
-
-// FromData will find and optionally verify Squareup secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Squareup,
- Raw: []byte(resMatch),
- }
-
- if verify {
-
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://connect.squareup.com/oauth2/authorize?client_id=%s&scope=CUSTOMERS_WRITE+CUSTOMERS_READ&session=False&state=82201dd8d83d23cc8a48caf52b", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Squareup
-}
-
-func (s Scanner) Description() string {
- return "Squareup is a financial services and mobile payment company. The detected key can be used to interact with Squareup's APIs for processing payments and accessing customer data."
-}
diff --git a/pkg/detectors/squareup/squareup_integration_test.go b/pkg/detectors/squareup/squareup_integration_test.go
deleted file mode 100644
index fce1cce50664..000000000000
--- a/pkg/detectors/squareup/squareup_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package squareup
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSquareup_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SQUAREUP")
- inactiveSecret := testSecrets.MustGetField("SQUAREUP_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a squareup secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Squareup,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a squareup secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Squareup,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Squareup.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Squareup.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/squareup/squareup_test.go b/pkg/detectors/squareup/squareup_test.go
deleted file mode 100644
index 787eee9c9257..000000000000
--- a/pkg/detectors/squareup/squareup_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package squareup
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "sq0idp-CIlelDA4a0mIksYXPGwUzy"
- invalidPattern = "sq0idp-CIlelDA?a0mIksYXPGwUzy"
- keyword = "squareup"
-)
-
-func TestSquareup_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword squareup",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sslmate/sslmate.go b/pkg/detectors/sslmate/sslmate.go
deleted file mode 100644
index 29d3023623c1..000000000000
--- a/pkg/detectors/sslmate/sslmate.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package sslmate
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sslmate"}) + `\b([a-zA-Z0-9]{36})\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{"sslmate"}
-}
-
-// FromData will find and optionally verify SslMate secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SslMate,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://sslmate.com/api/v2/certs/example.com?expand=current.crt", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SslMate
-}
-
-func (s Scanner) Description() string {
- return "SslMate is a service that helps manage and automate SSL certificates. SslMate API keys can be used to access and manage SSL certificates."
-}
diff --git a/pkg/detectors/sslmate/sslmate_integration_test.go b/pkg/detectors/sslmate/sslmate_integration_test.go
deleted file mode 100644
index 7c5f396604fb..000000000000
--- a/pkg/detectors/sslmate/sslmate_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package sslmate
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSslMate_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SSLMATE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SSLMATE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sslmate secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SslMate,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sslmate secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SslMate,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SslMate.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SslMate.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/sslmate/sslmate_test.go b/pkg/detectors/sslmate/sslmate_test.go
deleted file mode 100644
index ee9cf6e14a36..000000000000
--- a/pkg/detectors/sslmate/sslmate_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package sslmate
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "4RCKoI9YgcI43rpDbhyYpGdHtNaG1DLcwEYR"
- invalidPattern = "4RCKoI9YgcI43rpDbh?YpGdHtNaG1DLcwEYR"
- keyword = "sslmate"
-)
-
-func TestSslMate_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword sslmate",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/statuscake/statuscake.go b/pkg/detectors/statuscake/statuscake.go
deleted file mode 100644
index e06b3a42ada6..000000000000
--- a/pkg/detectors/statuscake/statuscake.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package statuscake
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"statuscake"}) + `\b([a-zA-Z0-9]{20})\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{"statuscake"}
-}
-
-// FromData will find and optionally verify Statuscake secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Statuscake,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.statuscake.com/v1/ssl", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Statuscake
-}
-
-func (s Scanner) Description() string {
- return "Statuscake is a website monitoring service. Statuscake API keys can be used to access and manage website monitoring configurations and data."
-}
diff --git a/pkg/detectors/statuscake/statuscake_integration_test.go b/pkg/detectors/statuscake/statuscake_integration_test.go
deleted file mode 100644
index 8cd8609cf9e0..000000000000
--- a/pkg/detectors/statuscake/statuscake_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package statuscake
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStatuscake_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STATUSCAKE")
- inactiveSecret := testSecrets.MustGetField("STATUSCAKE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a statuscake secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Statuscake,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a statuscake secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Statuscake,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Statuscake.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Statuscake.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/statuscake/statuscake_test.go b/pkg/detectors/statuscake/statuscake_test.go
deleted file mode 100644
index 041ae4efe70f..000000000000
--- a/pkg/detectors/statuscake/statuscake_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package statuscake
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "jwI4HrRAbQV4rUYYD76C"
- invalidPattern = "jwI4HrRAbQ?4rUYYD76C"
- keyword = "statuscake"
-)
-
-func TestStatuscake_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword statuscake",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/statuspage/statuspage.go b/pkg/detectors/statuspage/statuspage.go
deleted file mode 100644
index c980c6973aed..000000000000
--- a/pkg/detectors/statuspage/statuspage.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package statuspage
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"statuspage"}) + `\b([0-9a-z-]{36})\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{"statuspage"}
-}
-
-// FromData will find and optionally verify Statuspage secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Statuspage,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.statuspage.io/v1/pages", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Statuspage
-}
-
-func (s Scanner) Description() string {
- return "Statuspage is an incident communication tool that helps you inform your users about outages and scheduled maintenance. Statuspage API keys can be used to manage and update your status pages."
-}
diff --git a/pkg/detectors/statuspage/statuspage_integration_test.go b/pkg/detectors/statuspage/statuspage_integration_test.go
deleted file mode 100644
index c2c8335dc6c9..000000000000
--- a/pkg/detectors/statuspage/statuspage_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package statuspage
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStatuspage_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STATUSPAGE")
- inactiveSecret := testSecrets.MustGetField("STATUSPAGE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a statuspage secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Statuspage,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a statuspage secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Statuspage,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Statuspage.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Statuspage.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/statuspage/statuspage_test.go b/pkg/detectors/statuspage/statuspage_test.go
deleted file mode 100644
index 197ea035d41f..000000000000
--- a/pkg/detectors/statuspage/statuspage_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package statuspage
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "0c8xiakjyy42sep7fs17pzq08ra0e4aj04uq"
- invalidPattern = "0c8xiakjyy42sep7fs?7pzq08ra0e4aj04uq"
- keyword = "statuspage"
-)
-
-func TestStatuspage_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword statuspage",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/statuspal/statuspal.go b/pkg/detectors/statuspal/statuspal.go
deleted file mode 100644
index bdc7574b99b3..000000000000
--- a/pkg/detectors/statuspal/statuspal.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package statuspal
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"statuspal"}) + `\b([0-9a-zA-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"statuspal"}
-}
-
-// FromData will find and optionally verify Statuspal secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Statuspal,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://statuspal.io/api/v1/status_pages/secretscanner/subscriptions", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.statuspal+json; version=3")
- req.Header.Add("Authorization", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Statuspal
-}
-
-func (s Scanner) Description() string {
- return "Statuspal is a status page service. Statuspal API keys can be used to manage status pages and subscriptions."
-}
diff --git a/pkg/detectors/statuspal/statuspal_integration_test.go b/pkg/detectors/statuspal/statuspal_integration_test.go
deleted file mode 100644
index 2f1a9547b072..000000000000
--- a/pkg/detectors/statuspal/statuspal_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package statuspal
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStatuspal_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STATUSPAL")
- inactiveSecret := testSecrets.MustGetField("STATUSPAL_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a statuspal secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Statuspal,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a statuspal secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Statuspal,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Statuspal.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Statuspal.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/statuspal/statuspal_test.go b/pkg/detectors/statuspal/statuspal_test.go
deleted file mode 100644
index 10fcfee9aabf..000000000000
--- a/pkg/detectors/statuspal/statuspal_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package statuspal
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "FIYuLMuzF8XPqwPQzEiQFzJziMhcrAfH"
- invalidPattern = "FIYuLMu?F8XPqwPQzEiQFzJziMhcrAfH"
- keyword = "statuspal"
-)
-
-func TestStatuspal_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword statuspal",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/stitchdata/stitchdata.go b/pkg/detectors/stitchdata/stitchdata.go
deleted file mode 100644
index 44f33f2a23d7..000000000000
--- a/pkg/detectors/stitchdata/stitchdata.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package stitchdata
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"stitchdata"}) + `\b([0-9a-z_]{35})\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{"stitchdata"}
-}
-
-// FromData will find and optionally verify Stitchdata secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Stitchdata,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.stitchdata.com/v4/sources", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Stitchdata
-}
-
-func (s Scanner) Description() string {
- return "Stitchdata is a cloud ETL service for consolidating data from various sources into a data warehouse. Stitchdata API keys can be used to manage and automate data pipelines."
-}
diff --git a/pkg/detectors/stitchdata/stitchdata_integration_test.go b/pkg/detectors/stitchdata/stitchdata_integration_test.go
deleted file mode 100644
index e3f0c9a3f51a..000000000000
--- a/pkg/detectors/stitchdata/stitchdata_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package stitchdata
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStitchdata_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STITCHDATA")
- inactiveSecret := testSecrets.MustGetField("STITCHDATA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stitchdata secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stitchdata,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stitchdata secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stitchdata,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Stitchdata.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Stitchdata.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/stitchdata/stitchdata_test.go b/pkg/detectors/stitchdata/stitchdata_test.go
deleted file mode 100644
index fc3d40cd0c5c..000000000000
--- a/pkg/detectors/stitchdata/stitchdata_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package stitchdata
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "8n4bfv0w_ygprt3xkr3gz1ez6jmz0oo9eva"
- invalidPattern = "8n4bfv0w_ygp?t3xkr3gz1ez6jmz0oo9eva"
- keyword = "stitchdata"
-)
-
-func TestStitchdata_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword stitchdata",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/stockdata/stockdata.go b/pkg/detectors/stockdata/stockdata.go
deleted file mode 100644
index 03c2f073251e..000000000000
--- a/pkg/detectors/stockdata/stockdata.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package stockdata
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"stockdata"}) + `\b([0-9A-Za-z]{40})\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{"stockdata"}
-}
-
-// FromData will find and optionally verify Stockdata secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Stockdata,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.stockdata.org/v1/data/quote?symbols=AAPL,TSLA,MSFT&api_token="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Stockdata
-}
-
-func (s Scanner) Description() string {
- return "Stockdata is a service providing stock market data. Stockdata API keys can be used to access and retrieve stock market information."
-}
diff --git a/pkg/detectors/stockdata/stockdata_integration_test.go b/pkg/detectors/stockdata/stockdata_integration_test.go
deleted file mode 100644
index d7794858e433..000000000000
--- a/pkg/detectors/stockdata/stockdata_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package stockdata
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStockdata_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STOCKDATA")
- inactiveSecret := testSecrets.MustGetField("STOCKDATA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stockdata secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stockdata,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stockdata secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stockdata,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Stockdata.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Stockdata.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/stockdata/stockdata_test.go b/pkg/detectors/stockdata/stockdata_test.go
deleted file mode 100644
index 04bb01feffc8..000000000000
--- a/pkg/detectors/stockdata/stockdata_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package stockdata
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "94ny9HMpy5rGR7jI3Z2fjBQpuU8gQofhlw4u3sRr"
- invalidPattern = "94ny9HMpy5rGR7?I3Z2fjBQpuU8gQofhlw4u3sRr"
- keyword = "stockdata"
-)
-
-func TestStockdata_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword stockdata",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/storecove/storecove.go b/pkg/detectors/storecove/storecove.go
deleted file mode 100644
index de90b55e1bfa..000000000000
--- a/pkg/detectors/storecove/storecove.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package storecove
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"storecove"}) + `\b([a-zA-Z0-9_-]{43})\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{"storecove"}
-}
-
-// FromData will find and optionally verify Storecove secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Storecove,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.storecove.com/api/v2/discovery/identifiers", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Storecove
-}
-
-func (s Scanner) Description() string {
- return "Storecove is an API service for electronic invoicing. Storecove API keys can be used to access and manage electronic invoicing data."
-}
diff --git a/pkg/detectors/storecove/storecove_integration_test.go b/pkg/detectors/storecove/storecove_integration_test.go
deleted file mode 100644
index 5adfc8b7efda..000000000000
--- a/pkg/detectors/storecove/storecove_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package storecove
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStorecove_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STORECOVE")
- inactiveSecret := testSecrets.MustGetField("STORECOVE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a storecove secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Storecove,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a storecove secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Storecove,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Storecove.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Storecove.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/storecove/storecove_test.go b/pkg/detectors/storecove/storecove_test.go
deleted file mode 100644
index 7fbabecd9eeb..000000000000
--- a/pkg/detectors/storecove/storecove_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package storecove
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "yDDqj4B00b1m_HymYpqVRbPHWi7njN60NYsT0tpHvEe"
- invalidPattern = "yDDqj4B00?1m_HymYpqVRbPHWi7njN60NYsT0tpHvEe"
- keyword = "storecove"
-)
-
-func TestStorecove_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword storecove",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/stormboard/stormboard.go b/pkg/detectors/stormboard/stormboard.go
deleted file mode 100644
index b3cf0c582f0b..000000000000
--- a/pkg/detectors/stormboard/stormboard.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package stormboard
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"stormboard"}) + `\b([a-f0-9]{40})\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{"stormboard"}
-}
-
-// FromData will find and optionally verify Stormboard secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Stormboard,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.stormboard.com/users/profile", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-API-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Stormboard
-}
-
-func (s Scanner) Description() string {
- return "Stormboard is a digital workspace for brainstorming and collaboration. Stormboard API keys can be used to access and modify data within the workspace."
-}
diff --git a/pkg/detectors/stormboard/stormboard_integration_test.go b/pkg/detectors/stormboard/stormboard_integration_test.go
deleted file mode 100644
index cf537ef2350c..000000000000
--- a/pkg/detectors/stormboard/stormboard_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package stormboard
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStormboard_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STORMBOARD")
- inactiveSecret := testSecrets.MustGetField("STORMBOARD_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stormboard secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stormboard,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stormboard secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stormboard,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Stormboard.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Stormboard.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/stormboard/stormboard_test.go b/pkg/detectors/stormboard/stormboard_test.go
deleted file mode 100644
index ecc3d0fc6a09..000000000000
--- a/pkg/detectors/stormboard/stormboard_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package stormboard
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "46e7fa9b370dd56209a5610630a45bb9a54fa1e9"
- invalidPattern = "4?e7fa9b370dd56209a5610630a45bb9a54fa1e9"
- keyword = "stormboard"
-)
-
-func TestStormboard_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword stormboard",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/stormglass/stormglass.go b/pkg/detectors/stormglass/stormglass.go
deleted file mode 100644
index 420d4be98b6c..000000000000
--- a/pkg/detectors/stormglass/stormglass.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package stormglass
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"stormglass"}) + `\b([0-9Aa-z-]{73})\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{"stormglass"}
-}
-
-// FromData will find and optionally verify Stormglass secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Stormglass,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.stormglass.io/v2/weather/point?lat=58.7984&lng=17.8081¶ms=windSpeed", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Stormglass
-}
-
-func (s Scanner) Description() string {
- return "Stormglass is a weather data provider. Stormglass API keys can be used to access weather data services."
-}
diff --git a/pkg/detectors/stormglass/stormglass_integration_test.go b/pkg/detectors/stormglass/stormglass_integration_test.go
deleted file mode 100644
index 8bdb9dfb819f..000000000000
--- a/pkg/detectors/stormglass/stormglass_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package stormglass
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStormglass_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STORMGLASS")
- inactiveSecret := testSecrets.MustGetField("STORMGLASS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stormglass secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stormglass,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stormglass secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stormglass,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Stormglass.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Stormglass.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/stormglass/stormglass_test.go b/pkg/detectors/stormglass/stormglass_test.go
deleted file mode 100644
index 26198eee3e8d..000000000000
--- a/pkg/detectors/stormglass/stormglass_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package stormglass
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "1sijzovx0w8ru40qteilgbxej6a5A1moc7s9b1p3mx9472dho5svv5yA7b0aqzu-Ai-z-r1xz"
- invalidPattern = "1sijzovx0w8ru40qteilg?xej6a5A1moc7s9b1p3mx9472dho5svv5yA7b0aqzu-Ai-z-r1xz"
- keyword = "stormglass"
-)
-
-func TestStormglass_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword stormglass",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/storyblok/storyblok.go b/pkg/detectors/storyblok/storyblok.go
deleted file mode 100644
index d589ca279b0a..000000000000
--- a/pkg/detectors/storyblok/storyblok.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package storyblok
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"storyblok"}) + `\b([0-9A-Za-z]{22}tt)\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{"storyblok"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_StoryblokAccessToken
-}
-
-func (s Scanner) Description() string {
- return "Storyblok is a headless CMS that allows developers to build flexible and powerful content management solutions. Storyblok tokens can be used to access and modify content within a Storyblok space."
-}
-
-// FromData will find and optionally verify Storyblok secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueAccessTokens = make(map[string]struct{})
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueAccessTokens[match[1]] = struct{}{}
- }
-
- for accessToken := range uniqueAccessTokens {
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_StoryblokAccessToken,
- Raw: []byte(accessToken),
- }
-
- if verify {
- isVerified, verificationErr := verifyStoryBlokAccessToken(ctx, client, accessToken)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-// docs: https://www.storyblok.com/docs/api/content-delivery/v2/getting-started/authentication
-func verifyStoryBlokAccessToken(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.storyblok.com/v1/cdn/spaces/me/?token="+token, nil)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/storyblok/storyblok_integration_test.go b/pkg/detectors/storyblok/storyblok_integration_test.go
deleted file mode 100644
index b639cc04455d..000000000000
--- a/pkg/detectors/storyblok/storyblok_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package storyblok
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStoryblok_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STORYBLOK_TOKEN")
- inactiveSecret := testSecrets.MustGetField("STORYBLOK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a storyblok secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_StoryblokAccessToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a storyblok secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_StoryblokAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Storyblok.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Storyblok.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/storyblok/storyblok_test.go b/pkg/detectors/storyblok/storyblok_test.go
deleted file mode 100644
index 3afcca6b3f3e..000000000000
--- a/pkg/detectors/storyblok/storyblok_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package storyblok
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "ZXIic758xXghN5SOkRI57ytt"
- invalidPattern = "ZXIic758xXgh?5SOkRI57ytt"
- keyword = "storyblok"
-)
-
-func TestStoryblok_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword storyblok",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/storyblokpersonalaccesstoken/storyblok.go b/pkg/detectors/storyblokpersonalaccesstoken/storyblok.go
deleted file mode 100644
index 529ee0bd5276..000000000000
--- a/pkg/detectors/storyblokpersonalaccesstoken/storyblok.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package storyblokpersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"storyblok"}) + `\b([0-9A-Za-z]{22}tt-[0-9]{6}-[A-Za-z0-9_-]{20})\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{"storyblok"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_StoryblokPersonalAccessToken
-}
-
-func (s Scanner) Description() string {
- return `Storyblok is a headless CMS that allows developers to build flexible and powerful content management solutions.
- Storyblok personal access tokens can be used with management APIs.
- The Storyblok Management API allows you to create, edit, update, and delete content using a common interface`
-}
-
-// FromData will find and optionally verify Storyblok secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniquePATs = make(map[string]struct{})
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniquePATs[match[1]] = struct{}{}
- }
-
- for pat := range uniquePATs {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_StoryblokPersonalAccessToken,
- Raw: []byte(pat),
- }
-
- if verify {
- isVerified, verificationErr := verifyStoryBlokPersonalAccessToken(ctx, client, pat)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-// docs: http://storyblok.com/docs/api/management/core-resources/spaces/retrieve-multiple-spaces
-func verifyStoryBlokPersonalAccessToken(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://mapi.storyblok.com/v1/spaces/", nil)
- if err != nil {
- return false, err
- }
-
- // docs: https://www.storyblok.com/docs/api/management/getting-started/authentication
- req.Header.Set("Authorization", token)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/storyblokpersonalaccesstoken/storyblok_integration_test.go b/pkg/detectors/storyblokpersonalaccesstoken/storyblok_integration_test.go
deleted file mode 100644
index f3c5a46a8c2d..000000000000
--- a/pkg/detectors/storyblokpersonalaccesstoken/storyblok_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package storyblokpersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStoryblokPersonalAccessToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- secret := testSecrets.MustGetField("STORYBLOK_PAT")
- inactiveSecret := testSecrets.MustGetField("STORYBLOK_PAT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a storyblok secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_StoryblokPersonalAccessToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a storyblok secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_StoryblokPersonalAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Storyblok.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Storyblok.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/storyblokpersonalaccesstoken/storyblok_test.go b/pkg/detectors/storyblokpersonalaccesstoken/storyblok_test.go
deleted file mode 100644
index fb663e7067c8..000000000000
--- a/pkg/detectors/storyblokpersonalaccesstoken/storyblok_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package storyblokpersonalaccesstoken
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "5r7EgNfakeXi6ZoEls1twAtt-001100-Q5E2fKfakeRqsUjwmsJn"
- invalidPattern = "5r7EgNfakeXi6ZoEls1twAt-001100-Q5E2fKfakeRqsUjwmsJn"
- keyword = "storyblok"
-)
-
-func TestStoryblokPersonalAccessToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword storyblok",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/storychief/storychief.go b/pkg/detectors/storychief/storychief.go
deleted file mode 100644
index 36356fe4ec63..000000000000
--- a/pkg/detectors/storychief/storychief.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package storychief
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"storychief"}) + `\b([a-zA-Z0-9_\-.]{940,1000})`)
-)
-
-// 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{"storychief"}
-}
-
-// FromData will find and optionally verify Storychief secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Storychief,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.storychief.io/1.0/users?page=1&count=10", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Storychief
-}
-
-func (s Scanner) Description() string {
- return "Storychief is a content marketing platform that helps in creating, distributing, and measuring content. Storychief API keys can be used to access and manage content and integrations within the platform."
-}
diff --git a/pkg/detectors/storychief/storychief_integration_test.go b/pkg/detectors/storychief/storychief_integration_test.go
deleted file mode 100644
index dade17d2a6be..000000000000
--- a/pkg/detectors/storychief/storychief_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package storychief
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStorychief_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STORYCHIEF")
- inactiveSecret := testSecrets.MustGetField("STORYCHIEF_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a storychief secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Storychief,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a storychief secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Storychief,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Storychief.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Storychief.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/storychief/storychief_test.go b/pkg/detectors/storychief/storychief_test.go
deleted file mode 100644
index a03e5c203033..000000000000
--- a/pkg/detectors/storychief/storychief_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package storychief
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "OjhRmEboKs5kYVWJOrpTrloSRsAdkEl8PK-eHrSID5TRR.59-m5ezCY1dcJqJCxbV.2.LeGuqnhuouYkaiJ-iq90X-XX0J3IcT-ReLn.lAU0FfkvHOoOFp4k8w0-nDKAI8irzT5.pi7bmathhUdZO40-Rb59B6M0h40LbAkcvW49YP_-xXqGV_s.tCRbbzeUWt.Y9cFzfrQfRaVlTTqF5AC4mTEF4UxCSHK7uEE3OdzKhRehslviyKozUqer9HEZQ941rCqeEpt8kDcC0GOZFskrqu-EynCRJRhK6cv682e3HoRqE9x5FtcJ3gPfJEA70yHF5gTT0gh1KYDPKoJ-SQ7cjEBxxn-NbLu_WD3HW8DsjVDu1MgqrjqrnFNQvEAqYM7RI.RurVC38TP-5PioJqzJbvpNzkvwFjDrPpHdcivLgDEJUXS39PkIZPb1WCk.LPMOj5MBAwgn7TuADlV34Tael2FxygTPzA6ZVHBzm4aoUZ94Fwcm1vAkKJPeydsu33lmJ73e55pp7IhFyRO6MdPgLHqm3XkUhleU4yDUAEPWMyhilzOEO1t3nP3plfPjZU1.A1VEgWoOjvhs61qAMj2O6YsVFc7nU-PRlOpqj7yJNRmLWnJGVZW1UQLYwo5urJTb92u8BBPe179Eldzk5-xQ994NnrROnAK5DXkwdw9KII85fVBof8LGei1ocVEzYodTVvVY75iXaVmIP3Sf3dqkumW9Jdik-G-Lz.tvyJMTUSmtbX1oXaByyInng89h0Ah1O7D36nUm-gSOOgjJAssWCF0jiOSb2ps7BCdArjd5BVEOhewpodrtDs.iOncs8dtMSmyA5N7Jhzo2eINenb9dhJ7yQmskzhQcN-jpHKLpiL.w4lqCZ5X.uI_oDjx6V_7bJQK07uWCEB8xiwTRCCnB0mZYmi5q0WpG4sCY2xIW"
- invalidPattern = "O?hRmEboKs5kYVWJOrpTrloSRsAdkEl8PK-eHrSID5TRR.59-m5ezCY1dcJqJCxbV.2.LeGuqnhuouYkaiJ-iq90X-XX0J3IcT-ReLn.lAU0FfkvHOoOFp4k8w0-nDKAI8irzT5.pi7bmathhUdZO40-Rb59B6M0h40LbAkcvW49YP_-xXqGV_s.tCRbbzeUWt.Y9cFzfrQfRaVlTTqF5AC4mTEF4UxCSHK7uEE3OdzKhRehslviyKozUqer9HEZQ941rCqeEpt8kDcC0GOZFskrqu-EynCRJRhK6cv682e3HoRqE9x5FtcJ3gPfJEA70yHF5gTT0gh1KYDPKoJ-SQ7cjEBxxn-NbLu_WD3HW8DsjVDu1MgqrjqrnFNQvEAqYM7RI.RurVC38TP-5PioJqzJbvpNzkvwFjDrPpHdcivLgDEJUXS39PkIZPb1WCk.LPMOj5MBAwgn7TuADlV34Tael2FxygTPzA6ZVHBzm4aoUZ94Fwcm1vAkKJPeydsu33lmJ73e55pp7IhFyRO6MdPgLHqm3XkUhleU4yDUAEPWMyhilzOEO1t3nP3plfPjZU1.A1VEgWoOjvhs61qAMj2O6YsVFc7nU-PRlOpqj7yJNRmLWnJGVZW1UQLYwo5urJTb92u8BBPe179Eldzk5-xQ994NnrROnAK5DXkwdw9KII85fVBof8LGei1ocVEzYodTVvVY75iXaVmIP3Sf3dqkumW9Jdik-G-Lz.tvyJMTUSmtbX1oXaByyInng89h0Ah1O7D36nUm-gSOOgjJAssWCF0jiOSb2ps7BCdArjd5BVEOhewpodrtDs.iOncs8dtMSmyA5N7Jhzo2eINenb9dhJ7yQmskzhQcN-jpHKLpiL.w4lqCZ5X.uI_oDjx6V_7bJQK07uWCEB8xiwTRCCnB0mZYmi5q0WpG4sCY2xIW"
- keyword = "storychief"
-)
-
-func TestStorychief_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword storychief",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/strava/strava.go b/pkg/detectors/strava/strava.go
deleted file mode 100644
index 523291d09be9..000000000000
--- a/pkg/detectors/strava/strava.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package strava
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"strava"}) + `\b([0-9]{5})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"strava"}) + `\b([0-9a-z]{40})\b`)
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"strava"}) + `\b([0-9a-z]{40})\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{"strava"}
-}
-
-// FromData will find and optionally verify Strava secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
- keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range idMatches {
- resId := strings.TrimSpace(match[1])
-
- for _, secretMatch := range secretMatches {
- resSecret := strings.TrimSpace(secretMatch[1])
-
- for _, keyMatch := range keyMatches {
- resKey := strings.TrimSpace(keyMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Strava,
- Raw: []byte(resId),
- RawV2: []byte(resId + resSecret),
- }
-
- if verify {
- payload := strings.NewReader("grant_type=refresh_token&client_id=" + resId + "&client_secret=" + resSecret + "&refresh_token=" + resKey)
-
- req, err := http.NewRequestWithContext(ctx, "POST", "https://www.strava.com/oauth/token", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Strava
-}
-
-func (s Scanner) Description() string {
- return "An workout app, Oauth API keys can potentially be used to access user workout data"
-}
diff --git a/pkg/detectors/strava/strava_integration_test.go b/pkg/detectors/strava/strava_integration_test.go
deleted file mode 100644
index 908965540911..000000000000
--- a/pkg/detectors/strava/strava_integration_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package strava
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStrava_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- id := testSecrets.MustGetField("STRAVA_ID")
- secret := testSecrets.MustGetField("STRAVA_SECRET")
- token := testSecrets.MustGetField("STRAVA_TOKEN")
- inactiveSecret := testSecrets.MustGetField("STRAVA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a strava secret %s within strava id %s and strava token %s", secret, id, token)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Strava,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a strava secret %s within strava id %s and strava token %s but not valid", inactiveSecret, id, token)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Strava,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Strava.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Strava.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/strava/strava_test.go b/pkg/detectors/strava/strava_test.go
deleted file mode 100644
index 204f913823f0..000000000000
--- a/pkg/detectors/strava/strava_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package strava
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validId = "57732"
- invalidId = "5?732"
- validSecret = "ateo889bcja9fyt4gff2kqe187uhy8l0il4vf2cw"
- invalidSecret = "ateo889bcja9fyt4gff2?qe187uhy8l0il4vf2cw"
- validKey = "e8qmzsdc0qbq4hsal5m4q1pz5s2yloydbl1bjc1u"
- invalidKey = "e8?mzsdc0qbq4hsal5m4q1pz5s2yloydbl1bjc1u"
- keyword = "strava"
-)
-
-func TestStrava_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword strava",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, validId, keyword, validSecret, keyword, validKey),
- want: []string{validId + validSecret, validId + validKey, validId + validKey, validId + validSecret},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, invalidId, keyword, invalidSecret, keyword, invalidKey),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/streak/streak.go b/pkg/detectors/streak/streak.go
deleted file mode 100644
index 67cb5416ac05..000000000000
--- a/pkg/detectors/streak/streak.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package streak
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"streak"}) + `\b([0-9Aa-f]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"streak"}
-}
-
-// FromData will find and optionally verify Streak secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Streak,
- Raw: []byte(resMatch),
- }
-
- if verify {
- data := fmt.Sprintf("%s:", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.streak.com/api/v1/pipelines", nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Streak
-}
-
-func (s Scanner) Description() string {
- return "Streak is a CRM software that integrates with Gmail. Streak API keys can be used to access and manage CRM data within Streak."
-}
diff --git a/pkg/detectors/streak/streak_integration_test.go b/pkg/detectors/streak/streak_integration_test.go
deleted file mode 100644
index 38c4e7d45ed0..000000000000
--- a/pkg/detectors/streak/streak_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package streak
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStreak_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STREAK")
- inactiveSecret := testSecrets.MustGetField("STREAK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a streak secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Streak,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a streak secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Streak,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Streak.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Streak.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/streak/streak_test.go b/pkg/detectors/streak/streak_test.go
deleted file mode 100644
index ea1ffeff476a..000000000000
--- a/pkg/detectors/streak/streak_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package streak
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "bf1949fA2574fb6b2deb94AdA51f0719"
- invalidPattern = "bf1949fA?574fb6b2deb94AdA51f0719"
- keyword = "streak"
-)
-
-func TestStreak_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword streak",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/stripe/stripe.go b/pkg/detectors/stripe/stripe.go
deleted file mode 100644
index 79a5e8ac593d..000000000000
--- a/pkg/detectors/stripe/stripe.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package stripe
-
-import (
- "context"
- "fmt"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- // doesn't include test keys with "sk_test"
- secretKey = regexp.MustCompile(`[rs]k_live_[a-zA-Z0-9]{20,247}`)
-)
-
-// 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{"k_live"}
-}
-
-// FromData will find and optionally verify Stripe secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
-
- dataStr := string(data)
-
- matches := secretKey.FindAllString(dataStr, -1)
-
- for _, match := range matches {
-
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_Stripe,
- Raw: []byte(match),
- }
- result.ExtraData = map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/stripe/",
- }
-
- if verify {
-
- baseURL := "https://api.stripe.com/v1/charges"
-
- client := common.SaneHttpClient()
-
- // test `read_user` scope
- req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", match))
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- res.Body.Close() // The request body is unused.
-
- if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusForbidden {
- result.Verified = true
- }
- }
- result.AnalysisInfo = map[string]string{"key": match}
- }
-
- results = append(results, result)
- }
-
- return
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Stripe
-}
-
-func (s Scanner) Description() string {
- return "Stripe is a payment processing platform. Stripe API keys can be used to interact with Stripe's services for processing payments, managing subscriptions, and more."
-}
diff --git a/pkg/detectors/stripe/stripe_integration_test.go b/pkg/detectors/stripe/stripe_integration_test.go
deleted file mode 100644
index 19bb0836a460..000000000000
--- a/pkg/detectors/stripe/stripe_integration_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package stripe
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStripe_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STRIPE_SECRET")
- secretInactive := testSecrets.MustGetField("STRIPE_INACTIVE")
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stripe secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stripe,
- Verified: true,
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/stripe/",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stripe secret %s within", secretInactive)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stripe,
- Verified: false,
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/stripe/",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Stripe.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- got[i].AnalysisInfo = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Stripe.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/stripe/stripe_test.go b/pkg/detectors/stripe/stripe_test.go
deleted file mode 100644
index 5c6c9a046925..000000000000
--- a/pkg/detectors/stripe/stripe_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package stripe
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "rk_live_HUOlkIKhNEYOPS0oDSwGwJHbg4xGaXNeJZ2CdvDGeVZQHljoq5TuFwQHgME3W"
- invalidPattern = "?k_live_HUOlkIKhNEYOPS0oDSwGwJHbg4xGaXNeJZ2CdvDGeVZQHljoq5TuFwQHgME3?"
- keyword = "stripe"
-)
-
-func TestStripe_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword stripe",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/stripepaymentintent/stripepaymentintent.go b/pkg/detectors/stripepaymentintent/stripepaymentintent.go
deleted file mode 100644
index fd4da43e0dd7..000000000000
--- a/pkg/detectors/stripepaymentintent/stripepaymentintent.go
+++ /dev/null
@@ -1,219 +0,0 @@
-package stripepaymentintent
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- Client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- clientSecretPat = regexp.MustCompile(`\b(pi_[a-zA-Z0-9]{24}_secret_[a-zA-Z0-9]{25})\b`)
- secretKeyPat = regexp.MustCompile(`\b([rs]k_live_[a-zA-Z0-9]{20,247})\b`)
- publishableKeyPat = regexp.MustCompile(`\b(pk_live_[a-zA-Z0-9]{20,247})\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{"_secret_"}
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.Client != nil {
- return s.Client
- }
- return defaultClient
-}
-
-// FromData will find and optionally verify Stripe Payment Intent secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {
- dataStr := string(data)
-
- // Stripe client secrets can't be verified on their own, they must be paired with a secret or publishable key.
- // Secret keys are preferred for verification, but in some real-world cases only publishable keys are present.
- // While typically used client-side, publishable keys can still confirm certain PaymentIntents.
- // To avoid missing valid detections, we verify using both key types.
- // If no keys are found, we skip detection since client secrets alone are not actionable.
- clientSecrets := extractMatches(clientSecretPat, dataStr)
- secretKeys := extractMatches(secretKeyPat, dataStr)
- publishableKeys := extractMatches(publishableKeyPat, dataStr)
-
- results := make([]detectors.Result, 0, len(clientSecrets)*(len(secretKeys)+len(publishableKeys)))
-
- // Process each client secret against all keys
- for clientSecret := range clientSecrets {
- for key := range secretKeys {
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_StripePaymentIntent,
- Raw: []byte(clientSecret),
- RawV2: []byte(clientSecret + key),
- ExtraData: map[string]string{
- "key_type": "secret",
- },
- }
-
- if verify {
- verified, err := verifyPaymentIntentWithSecretKey(ctx, s.getClient(), clientSecret, key)
- result.Verified = verified
- result.SetVerificationError(err)
- }
-
- results = append(results, result)
- }
-
- for key := range publishableKeys {
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_StripePaymentIntent,
- Raw: []byte(clientSecret),
- RawV2: []byte(clientSecret + key),
- ExtraData: map[string]string{
- "key_type": "publishable",
- },
- }
-
- if verify {
- verified, err := verifyPaymentIntentWithPublishableKey(ctx, s.getClient(), clientSecret, key)
- result.Verified = verified
- result.SetVerificationError(err)
- }
-
- results = append(results, result)
- }
- }
-
- return results, nil
-}
-
-// Helper function to extract matches into a map for uniqueness
-func extractMatches(pattern *regexp.Regexp, data string) map[string]struct{} {
- matches := pattern.FindAllStringSubmatch(data, -1)
- result := make(map[string]struct{}, len(matches))
-
- for _, match := range matches {
- if len(match) >= 2 {
- result[match[1]] = struct{}{}
- }
- }
-
- return result
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_StripePaymentIntent
-}
-
-func (s Scanner) Description() string {
- return "Stripepaymentintent objects represent a customer's intent to pay and track the lifecycle of a payment. These objects are used to initiate and manage payment flows, including confirmation, authentication, and capture of funds."
-}
-
-// VerifyPaymentIntentWithSecretKey verifies a Stripe PaymentIntent using the secret key.
-// It checks if the PaymentIntent ID is valid and if the secret key has access to it.
-// It returns a VerificationResult indicating the validity of the PaymentIntent and any error messages.
-func verifyPaymentIntentWithSecretKey(ctx context.Context, client *http.Client, clientSecret, secretKey string) (bool, error) {
- url := fmt.Sprintf("https://api.stripe.com/v1/payment_intents/%s", extractIntentID(clientSecret))
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
- if err != nil {
- return false, fmt.Errorf("error creating request: %v", err)
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", secretKey))
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-
- resp, err := client.Do(req)
- if err != nil {
- return false, fmt.Errorf("request failed: %v", err)
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return isClientSecretValid(resp.Body, clientSecret)
- case http.StatusUnauthorized, http.StatusNotFound:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-// verifyPaymentIntentWithPublishableKey verifies a Stripe PaymentIntent using the publishable key.
-// It checks if the PaymentIntent ID is valid and if the publishable key has access to it.
-// It returns a VerificationResult indicating the validity of the PaymentIntent and any error messages.
-// Note: It should only be used for client-side verification or in scenarios where the secret key is unavailable.
-func verifyPaymentIntentWithPublishableKey(ctx context.Context, client *http.Client, clientSecret, publishableKey string) (bool, error) {
- paymentIntentId := extractIntentID(clientSecret)
- if paymentIntentId == "" {
- return false, fmt.Errorf("payment intent ID is required")
- }
-
- // Construct the request URL and add publishable key as a query parameter (this is how Stripe.js works)
- url := fmt.Sprintf("https://api.stripe.com/v1/payment_intents/%s", paymentIntentId)
- url = url + fmt.Sprintf("?key=%s", publishableKey)
- url = url + fmt.Sprintf("&client_secret=%s", clientSecret)
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
- if err != nil {
- return false, fmt.Errorf("error creating request: %v", err)
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, fmt.Errorf("request failed: %v", err)
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return isClientSecretValid(resp.Body, clientSecret)
- case http.StatusUnauthorized, http.StatusNotFound:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
-
-func extractIntentID(clientSecret string) string {
- parts := strings.SplitN(clientSecret, "_secret_", 2)
- if len(parts) != 2 {
- return ""
- }
- return parts[0]
-}
-
-func isClientSecretValid(body io.Reader, expectedSecret string) (bool, error) {
- var respBody struct {
- ClientSecret string `json:"client_secret"`
- }
-
- if err := json.NewDecoder(body).Decode(&respBody); err != nil {
- return false, fmt.Errorf("failed to decode response body: %v", err)
- }
-
- return respBody.ClientSecret == expectedSecret, nil
-}
diff --git a/pkg/detectors/stripepaymentintent/stripepaymentintent_integration_test.go b/pkg/detectors/stripepaymentintent/stripepaymentintent_integration_test.go
deleted file mode 100644
index ade83e949365..000000000000
--- a/pkg/detectors/stripepaymentintent/stripepaymentintent_integration_test.go
+++ /dev/null
@@ -1,146 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package stripepaymentintent
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStripepaymentintent_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STRIPE_SECRET")
- paymentIntent := testSecrets.MustGetField("STRIPE_PAYMENT_INTENT")
- secretInactive := testSecrets.MustGetField("STRIPE_INACTIVE")
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- wantVerified bool // Instead of expecting exact results, check if any result is verified
- wantResultCount int // Expected number of results
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stripepaymentintent secret %s and payment intent: %s within", secret, paymentIntent)),
- verify: true,
- },
- wantVerified: true, // At least one result should be verified
- wantResultCount: 1, // 1 client secret × 1 key = 1 result
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stripepaymentintent secret %s and payment intent %s within but not valid", secretInactive, paymentIntent)),
- verify: true,
- },
- wantVerified: false, // No results should be verified
- wantResultCount: 1, // 1 client secret × 1 key = 1 result
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- wantVerified: false,
- wantResultCount: 0, // No results expected
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Stripepaymentintent.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Check result count
- if len(got) != tt.wantResultCount {
- t.Errorf("Stripepaymentintent.FromData() got %d results, want %d", len(got), tt.wantResultCount)
- return
- }
-
- // Check each result
- hasVerified := false
- for i := range got {
- // Check that all results have the correct detector type
- if got[i].DetectorType != detectorspb.DetectorType_StripePaymentIntent {
- t.Errorf("Stripepaymentintent.FromData() result %d has wrong DetectorType", i)
- }
-
- // Check that raw secret is present
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present in result %d: \n %+v", i, got[i])
- }
-
- // Check that RawV2 is present (should contain client secret + key)
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no rawV2 present in result %d: \n %+v", i, got[i])
- }
-
- // Check verification error expectation
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
-
- // Track if any result is verified
- if got[i].Verified {
- hasVerified = true
- }
- }
-
- // Check verification expectation
- if hasVerified != tt.wantVerified {
- t.Errorf("Stripepaymentintent.FromData() hasVerified = %v, want %v", hasVerified, tt.wantVerified)
- }
- })
- }
-}
-
-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)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/stripepaymentintent/stripepaymentintent_test.go b/pkg/detectors/stripepaymentintent/stripepaymentintent_test.go
deleted file mode 100644
index 18843bbc5508..000000000000
--- a/pkg/detectors/stripepaymentintent/stripepaymentintent_test.go
+++ /dev/null
@@ -1,185 +0,0 @@
-package stripepaymentintent
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestStripepaymentintent_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- expectedPairs []string
- }{
- {
- name: "client secret with secret key",
- input: "stripepaymentintent_token = '" + "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "' and stripe_key = '" + "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" + "'",
- expectedPairs: []string{"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "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"},
- },
- {
- name: "client secret with publishable key",
- input: "stripepaymentintent_token = '" + "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "' and stripe_key = '" + "pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456" + "'",
- expectedPairs: []string{"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456"},
- },
- {
- name: "multiple client secrets with single key",
- input: `stripepaymentintent_token1 = '` + "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" + `'
- stripepaymentintent_token2 = '` + "pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456" + `'
- stripe_key = '` + "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" + `'`,
- expectedPairs: []string{
- "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "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",
- "pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456" +
- "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",
- },
- },
- {
- name: "single client secret with multiple keys",
- input: `stripepaymentintent_token = '` + "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" + `'
- stripe_secret_key = '` + "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" + `'
- stripe_publishable_key = '` + "pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456" + `'`,
- expectedPairs: []string{
- "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "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",
- "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456",
- },
- },
- {
- name: "multiple client secrets with multiple keys",
- input: `stripepaymentintent_token1 = '` + "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" + `'
- stripepaymentintent_token2 = '` + "pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456" + `'
- stripe_secret_key = '` + "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" + `'
- stripe_publishable_key = '` + "pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456" + `'`,
- expectedPairs: []string{
- "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "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",
- "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456",
- "pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456" +
- "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",
- "pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456" +
- "pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456",
- },
- },
- {
- name: "only client secret without keys",
- input: "stripepaymentintent_token = '" + "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" + "'",
- expectedPairs: []string{},
- },
- {
- name: "only keys without client secret",
- input: "stripe_key = '" + "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" + "'",
- expectedPairs: []string{},
- },
- {
- name: "invalid client secret with valid key",
- input: "stripepaymentintent_token = '" + "test_secret_test_1234567890abcdefg" + "' and stripe_key = '" +
- "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" + "'",
- expectedPairs: []string{},
- },
- {
- name: "mixed valid and invalid client secrets with key",
- input: `some_token = '` + "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" + `'
- other_token = '` + "test_secret_test_1234567890abcdefg" + `'
- stripe_key = '` + "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" + `'`,
- expectedPairs: []string{"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "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"},
- },
- {
- name: "complex scenario with multiple valid combinations",
- input: `
- # Multiple client secrets
- pi_token_1 = '` + "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" + `'
- pi_token_2 = '` + "pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456" + `'
-
- # Multiple secret keys
- secret_key_1 = '` + "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" + `'
- secret_key_2 = '` + "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" + `'
-
- # Multiple publishable keys
- pub_key_1 = '` + "pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456" + `'
- pub_key_2 = '` + "pk_live_51NuxCxMleJwHu7ix0BItRxY7OXh9Gr8RLr6ZHGz8XHCrSGIeUeF9MgLdLaRl8oKw4y6ZKwoKZHHa2o4HzGeIK6abc123456" + `'`,
- expectedPairs: []string{
- "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "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",
- "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "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",
- "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456",
- "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" +
- "pk_live_51NuxCxMleJwHu7ix0BItRxY7OXh9Gr8RLr6ZHGz8XHCrSGIeUeF9MgLdLaRl8oKw4y6ZKwoKZHHa2o4HzGeIK6abc123456",
- "pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456" +
- "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",
- "pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456" +
- "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",
- "pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456" +
- "pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456",
- "pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456" +
- "pk_live_51NuxCxMleJwHu7ix0BItRxY7OXh9Gr8RLr6ZHGz8XHCrSGIeUeF9MgLdLaRl8oKw4y6ZKwoKZHHa2o4HzGeIK6abc123456",
- },
- },
- {
- name: "test keys should not match (only live keys are detected)",
- input: `stripepaymentintent_token = '` + "pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456" + `'
- test_secret_key = 'sk_test_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5nJGydHjE'
- test_pub_key = 'pk_test_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5nJGydHjE'`,
- expectedPairs: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(test.expectedPairs) > 0 {
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.expectedPairs) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.expectedPairs), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.expectedPairs))
- for _, v := range test.expectedPairs {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/stripo/stripo.go b/pkg/detectors/stripo/stripo.go
deleted file mode 100644
index 562a45b77e0e..000000000000
--- a/pkg/detectors/stripo/stripo.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package stripo
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // JWT token
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"stripo"}) + `\b(eyJhbGciOiJIUzI1NiJ9\.[0-9A-Za-z]{130}\.[0-9A-Za-z_-]{43})\b`)
-)
-
-func (s Scanner) Keywords() []string {
- return []string{"stripo"}
-}
-
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Stripo,
- Raw: []byte(resMatch),
- }
-
- if verify {
-
- // API docs: https://api.stripo.email/reference/findemails
- client := s.client
- if client == nil {
- client = defaultClient
- }
- req, err := http.NewRequestWithContext(ctx, "GET", "https://stripo.email/emailgeneration/v1/emails?parameters=sortingAsc=true", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Stripo-Api-Auth", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- } else if res.StatusCode == 401 {
- // The secret is determinately not verified (nothing to do)
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Stripo
-}
-
-func (s Scanner) Description() string {
- return "Stripo is an email template builder. Stripo API keys can be used to access and modify email templates."
-}
diff --git a/pkg/detectors/stripo/stripo_integration_test.go b/pkg/detectors/stripo/stripo_integration_test.go
deleted file mode 100644
index ae2bb3c72d60..000000000000
--- a/pkg/detectors/stripo/stripo_integration_test.go
+++ /dev/null
@@ -1,127 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package stripo
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStripo_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STRIPO")
- inactiveSecret := testSecrets.MustGetField("STRIPO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stripo secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stripo,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stripo secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stripo,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Stripo.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Stripo.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/stripo/stripo_test.go b/pkg/detectors/stripo/stripo_test.go
deleted file mode 100644
index fd66f01bddb3..000000000000
--- a/pkg/detectors/stripo/stripo_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package stripo
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "eyJhbGciOiJIUzI1NiJ9.qflXV62P6Y51gAFD9pnq9FpcgDQOfr68GtBUARUvk7gzbAfg6eF66Txb4R9idT41JsG5CqTKXMfMnzpPVByJRFjtfXIFasywYCd53PqB774bhbBFgfEznjdQjOu8ui910q.8QQbrVsL16ya98sn8yyjLbBX21bix2G_QhFg_rKN9Bv"
- invalidPattern = "ayJhbGciOiJIUzI1NiJ9.qflXV62P6Y51gAFD9pnq9FpcgDQOfr68GtBUARUvk7gzbAfg6eF66Txb4R9idT41JsG5CqTKXMfMnzpPVByJRFjtfXIFasywYCd53PqB774bhbBFgfEznjdQjOu8ui910q.8QQbrVsL16ya98sn8yyjLbBX21bix2G_QhFg_rKN9Bv"
- keyword = "stripo"
-)
-
-func TestStripo_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword stripo",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/stytch/stytch.go b/pkg/detectors/stytch/stytch.go
deleted file mode 100644
index 3f237091dc4a..000000000000
--- a/pkg/detectors/stytch/stytch.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package stytch
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"stytch"}) + `\b([a-zA-Z0-9-_]{47}=)`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"stytch"}) + `\b([a-z0-9-]{49})\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{"stytch"}
-}
-
-// FromData will find and optionally verify Stytch secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- tokenPatMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- userPatMatch := strings.TrimSpace(idMatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Stytch,
- Raw: []byte(tokenPatMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.stytch.com/v1/users/pending", nil)
- if err != nil {
- continue
- }
- req.SetBasicAuth(userPatMatch, tokenPatMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Stytch
-}
-
-func (s Scanner) Description() string {
- return "Stytch is a platform for passwordless authentication. Stytch API keys can be used to access and manage authentication services."
-}
diff --git a/pkg/detectors/stytch/stytch_integration_test.go b/pkg/detectors/stytch/stytch_integration_test.go
deleted file mode 100644
index 6de25ac8a30a..000000000000
--- a/pkg/detectors/stytch/stytch_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package stytch
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestStytch_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("STYTCH")
- id := testSecrets.MustGetField("STYTCH_ID")
- inactiveSecret := testSecrets.MustGetField("STYTCH_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stytch secret %s within stytch %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stytch,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a stytch secret %s within stytch %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Stytch,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Stytch.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Stytch.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/stytch/stytch_test.go b/pkg/detectors/stytch/stytch_test.go
deleted file mode 100644
index 5a6d09c3e9a6..000000000000
--- a/pkg/detectors/stytch/stytch_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package stytch
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "TSIDpkOi1MKTudKSFs-KXGdOeH3ECTeXWtX7OAr4Y9k31m8="
- invalidKey = "TSIDpkOi1MKTudKSFs-KXGdO?H3ECTeXWtX7OAr4Y9k31m8="
- validId = "k7hi7kqgb6a8rqi0b8nbna1p-o32rv9xzlnvzbd5oa9yr-vtp"
- invalidId = "k7hi7kqgb6a8rqi0b8nbna1p?o32rv9xzlnvzbd5oa9yr-vtp"
- keyword = "stytch"
-)
-
-func TestStytch_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword stytch",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sugester/sugester.go b/pkg/detectors/sugester/sugester.go
deleted file mode 100644
index eec6e53ba933..000000000000
--- a/pkg/detectors/sugester/sugester.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package sugester
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sugester"}) + `\b([a-zA-Z0-9]{32})\b`)
- domainPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sugester"}) + `\b([a-zA-Z0-9_.!+$#^*%]{3,32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"sugester"}
-}
-
-// FromData will find and optionally verify Sugester secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- domainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, domainmatch := range domainMatches {
- resDomainMatch := strings.TrimSpace(domainmatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Sugester,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://"+resDomainMatch+".sugester.com/app/clients.json?api_token="+resMatch, nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.sugester+json; version=3")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Sugester
-}
-
-func (s Scanner) Description() string {
- return "Sugester is a customer support software that offers various tools for managing customer interactions. Sugester API keys can be used to access and manage data within the Sugester platform."
-}
diff --git a/pkg/detectors/sugester/sugester_integration_test.go b/pkg/detectors/sugester/sugester_integration_test.go
deleted file mode 100644
index 4b27ef9a3213..000000000000
--- a/pkg/detectors/sugester/sugester_integration_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package sugester
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSugester_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SUGESTER")
- domain := testSecrets.MustGetField("SUGESTER_DOMAIN")
- inactiveSecret := testSecrets.MustGetField("SUGESTER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sugester secret %s within sugesterdomain %s", secret, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sugester,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sugester secret %s within sugesterdomain %s but not valid", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Sugester,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Sugester.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Sugester.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/sugester/sugester_test.go b/pkg/detectors/sugester/sugester_test.go
deleted file mode 100644
index aa9d07dfaa4f..000000000000
--- a/pkg/detectors/sugester/sugester_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package sugester
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "RfQEMjlVIolNGUebkCS7iw0MevjgAD06"
- invalidKey = "RfQEMjlVIolNGUeb?CS7iw0MevjgAD06"
- validDomain = "h$6o0+jTX"
- invalidDomain = "?$6o0+jT?"
- keyword = "sugester"
-)
-
-func TestSugester_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword sugester",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validDomain),
- want: []string{validKey, validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidDomain),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/sumologickey/sumologickey.go b/pkg/detectors/sumologickey/sumologickey.go
deleted file mode 100644
index dff1768aa34d..000000000000
--- a/pkg/detectors/sumologickey/sumologickey.go
+++ /dev/null
@@ -1,174 +0,0 @@
-package sumologickey
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-
- regexp "github.com/wasilibs/go-re2"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.EndpointSetter
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var (
- _ detectors.Detector = (*Scanner)(nil)
- _ detectors.EndpointCustomizer = (*Scanner)(nil)
-)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Detect which instance the key is associated with.
- // https://help.sumologic.com/docs/api/getting-started/#documentation
- urlPat = regexp.MustCompile(`(?i)api\.(?:au|ca|de|eu|fed|jp|kr|in|us2)\.sumologic\.com`)
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sumo", "accessId"}) + `\b(su[A-Za-z0-9]{12})\b`)
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sumo", "accessKey"}) + `\b([A-Za-z0-9]{64})\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{"sumo", "accessId", "accessKey"}
-}
-
-// Default US API endpoint.
-func (Scanner) CloudEndpoint() string { return "api.sumologic.com" }
-
-// FromData will find and optionally verify SumoLogicKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- idMatches := make(map[string]struct{})
- for _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {
- idMatches[match[1]] = struct{}{}
- }
- keyMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatches[match[1]] = struct{}{}
- }
- endpointMatches := make(map[string]struct{})
- for _, match := range urlPat.FindAllStringSubmatch(dataStr, -1) {
- endpointMatches[match[0]] = struct{}{}
- }
- if len(endpointMatches) == 0 {
- endpointMatches[s.CloudEndpoint()] = struct{}{}
- }
-
- for accessKey := range keyMatches {
- var (
- r *detectors.Result
- accessId string
- apiEndpoint string
- )
-
- for id := range idMatches {
- accessId = id
-
- for e := range endpointMatches {
- apiEndpoint = e
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyMatch(ctx, client, apiEndpoint, accessId, accessKey)
- if isVerified {
- r = createResult(accessId, accessKey, apiEndpoint, isVerified, verificationErr)
- }
- }
- }
- }
-
- if r == nil {
- // Only include the accessId if we're confident which one it is.
- if len(idMatches) != 1 {
- accessId = ""
- }
- if len(endpointMatches) != 1 || apiEndpoint == s.CloudEndpoint() {
- apiEndpoint = ""
- }
- r = createResult(accessId, accessKey, apiEndpoint, false, nil)
- }
- results = append(results, *r)
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, endpoint string, id string, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://%s/api/v1/users", endpoint), nil)
- if err != nil {
- return false, err
- }
-
- req.SetBasicAuth(id, key)
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- // If the endpoint returns useful information, we can return it as a map.
- return true, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func createResult(accessId string, accessKey string, endpoint string, verified bool, err error) *detectors.Result {
- r := &detectors.Result{
- DetectorType: detectorspb.DetectorType_SumoLogicKey,
- Raw: []byte(accessKey),
- Verified: verified,
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/sumologic/",
- },
- }
- r.SetVerificationError(err, accessKey)
-
- // |endpoint| and |accessId| won't be specified unless there's a confident match.
- if accessId != "" {
- var sb strings.Builder
- sb.WriteString(`{`)
- sb.WriteString(`"accessId":"` + accessId + `"`)
- sb.WriteString(`,"accessKey":"` + accessKey + `"`)
- if endpoint != "" {
- sb.WriteString(`,"url":"` + endpoint + `"`)
- }
- sb.WriteString(`}`)
- r.RawV2 = []byte(sb.String())
- }
-
- return r
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SumoLogicKey
-}
-
-func (s Scanner) Description() string {
- return "Sumo Logic is a cloud-based machine data analytics service. Sumo Logic keys can be used to access and manage data within the Sumo Logic platform."
-}
diff --git a/pkg/detectors/sumologickey/sumologickey_integration_test.go b/pkg/detectors/sumologickey/sumologickey_integration_test.go
deleted file mode 100644
index 4776f5b49c4d..000000000000
--- a/pkg/detectors/sumologickey/sumologickey_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package sumologickey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSumoLogicKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- id := testSecrets.MustGetField("SUMOLOGIC_ACCESSID")
- secret := testSecrets.MustGetField("SUMOLOGIC_ACCESSKEY")
- inactiveId := testSecrets.MustGetField("SUMOLOGIC_ACCESSKEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sumologickey secret %s within %s", id, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SumoLogicKey,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a sumologickey secret %s within %s but not valid", inactiveId, secret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SumoLogicKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SumoLogicKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SumoLogicKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/sumologickey/sumologickey_test.go b/pkg/detectors/sumologickey/sumologickey_test.go
deleted file mode 100644
index 2e0c8f1d8d58..000000000000
--- a/pkg/detectors/sumologickey/sumologickey_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package sumologickey
-
-import (
- "context"
- "testing"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-
- "github.com/google/go-cmp/cmp"
-)
-
-func TestSumoLogicKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: `sumologic:
- accessId: suDkVYKjXZAwsz
- accessKey: Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK70a0LckgRSCL
- clusterName: Kubernetes_cluster-2024-10-25T21:34:23.096Z`,
- want: []string{`{"accessId":"suDkVYKjXZAwsz","accessKey":"Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK70a0LckgRSCL"}`},
- },
- {
- name: "pattern with url",
- input: `sumologic:
- baseUrl: api.us2.sumologic.com
- accessId: suDkVYKjXZAwsz
- accessKey: Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK70a0LckgRSCL
- clusterName: Kubernetes_cluster-2024-10-25T21:34:23.096Z`,
- want: []string{`{"accessId":"suDkVYKjXZAwsz","accessKey":"Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK70a0LckgRSCL","url":"api.us2.sumologic.com"}`},
- },
- {
- name: "finds all matches",
- input: `sumoId1 = 'suaRYt6iLL8cxl'
-sumoKey1 = 'CzrMhR8zzy1eH1F0XlY1tu5ywqa2yaSFoWGg2cqE43XkfnUVCytnPQfv1enUYrzv'
-sumoId2 = 'suDkVYKjXZBwsz'
-sumoKey2 = 'Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK21a0LckgRSCL'`,
- want: []string{"CzrMhR8zzy1eH1F0XlY1tu5ywqa2yaSFoWGg2cqE43XkfnUVCytnPQfv1enUYrzv", "Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK21a0LckgRSCL"},
- },
- {
- name: "invalid pattern",
- input: "sumoId = 'doDkVYKjXZAwsz'",
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/supabasetoken/supabasetoken.go b/pkg/detectors/supabasetoken/supabasetoken.go
deleted file mode 100644
index d904f4e005c2..000000000000
--- a/pkg/detectors/supabasetoken/supabasetoken.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package supabasetoken
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(sbp_[a-z0-9]{40})\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{"sbp_"}
-}
-
-// FromData will find and optionally verify Supabasetoken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SupabaseToken,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.supabase.com/v1/projects", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SupabaseToken
-}
-
-func (s Scanner) Description() string {
- return "Supabase is an open source Firebase alternative. Supabase tokens can be used to access and manage Supabase projects and data."
-}
diff --git a/pkg/detectors/supabasetoken/supabasetoken_integration_test.go b/pkg/detectors/supabasetoken/supabasetoken_integration_test.go
deleted file mode 100644
index 5c97a18adf71..000000000000
--- a/pkg/detectors/supabasetoken/supabasetoken_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package supabasetoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSupabasetoken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SUPABASETOKEN")
- inactiveSecret := testSecrets.MustGetField("SUPABASETOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a supabasetoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SupabaseToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a supabasetoken secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SupabaseToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Supabasetoken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Supabasetoken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/supabasetoken/supabasetoken_test.go b/pkg/detectors/supabasetoken/supabasetoken_test.go
deleted file mode 100644
index dcdb9a406c66..000000000000
--- a/pkg/detectors/supabasetoken/supabasetoken_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package supabasetoken
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "sbp_6corwd8ltv0m2hx5av0bqswrldi43hk8qdtnxcs7"
- invalidPattern = "sbp_6corwd8ltv?m2hx5av0bqswrldi43hk8qdtnxcs7"
- keyword = "supabasetoken"
-)
-
-func TestSupabasetoken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword supabasetoken",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/supernotesapi/supernotesapi.go b/pkg/detectors/supernotesapi/supernotesapi.go
deleted file mode 100644
index f10020095301..000000000000
--- a/pkg/detectors/supernotesapi/supernotesapi.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package supernotesapi
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"supernotes"}) + `([ \r\n]{0,1}[0-9A-Za-z\-_]{43}[ \r\n]{1})`)
-)
-
-// 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{"supernotes"}
-}
-
-// FromData will find and optionally verify SuperNotesAPI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SuperNotesAPI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.supernotes.app/v1/user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Api-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SuperNotesAPI
-}
-
-func (s Scanner) Description() string {
- return "SuperNotes is a note-taking application. SuperNotes API keys can be used to access and manage user notes and other data within the application."
-}
diff --git a/pkg/detectors/supernotesapi/supernotesapi_integration_test.go b/pkg/detectors/supernotesapi/supernotesapi_integration_test.go
deleted file mode 100644
index cf6a075a195a..000000000000
--- a/pkg/detectors/supernotesapi/supernotesapi_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package supernotesapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSuperNotesAPI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SUPERNOTESAPI")
- inactiveSecret := testSecrets.MustGetField("SUPERNOTESAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a supernotes secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SuperNotesAPI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a supernotes secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SuperNotesAPI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SuperNotesAPI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SuperNotesAPI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/supernotesapi/supernotesapi_test.go b/pkg/detectors/supernotesapi/supernotesapi_test.go
deleted file mode 100644
index 43e926d9ad79..000000000000
--- a/pkg/detectors/supernotesapi/supernotesapi_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package supernotesapi
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "V77f640DIDmrPcYFMwCKPhAR7CkXDwXjmMGpzHVm6yC"
- invalidPattern = "V77f640DIDmrPcYFMwCKPh?R7CkXDwXjmMGpzHVm6yC"
- keyword = "supernotesapi"
-)
-
-func TestSuperNotesAPI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword supernotesapi",
- input: fmt.Sprintf("%s token = ' %s '", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = ' %s ' | ' %s '", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = ' %s '", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = ' %s '", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/surveyanyplace/surveyanyplace.go b/pkg/detectors/surveyanyplace/surveyanyplace.go
deleted file mode 100644
index 6f315b5a16b0..000000000000
--- a/pkg/detectors/surveyanyplace/surveyanyplace.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package surveyanyplace
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"survey"}) + `\b([a-z0-9A-Z]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"survey"}) + `\b([a-z0-9A-Z-]{36})\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{"surveyanyplace"}
-}
-
-// FromData will find and optionally verify SurveyAnyplace secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SurveyAnyplace,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{
- "codes": [
- "code1",
- "code2"
- ]
- }`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.surveyanyplace.com/v1/surveys/"+resIdMatch+"/accesscodes", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("API %s", resMatch))
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SurveyAnyplace
-}
-
-func (s Scanner) Description() string {
- return "SurveyAnyplace is a platform for creating surveys and quizzes. The detected credential can be used to access and manage surveys on this platform."
-}
diff --git a/pkg/detectors/surveyanyplace/surveyanyplace_integration_test.go b/pkg/detectors/surveyanyplace/surveyanyplace_integration_test.go
deleted file mode 100644
index 6a849f3e8f80..000000000000
--- a/pkg/detectors/surveyanyplace/surveyanyplace_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package surveyanyplace
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSurveyAnyplace_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SURVEYANYPLACE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SURVEYANYPLACE_INACTIVE")
- id := testSecrets.MustGetField("SURVEYANYPLACE_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a surveyanyplace secret %s within surveyanyplaceid %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SurveyAnyplace,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a surveyanyplace secret %s within surveyanyplaceid %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SurveyAnyplace,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SurveyAnyplace.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SurveyAnyplace.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/surveyanyplace/surveyanyplace_test.go b/pkg/detectors/surveyanyplace/surveyanyplace_test.go
deleted file mode 100644
index 538ec420d2e5..000000000000
--- a/pkg/detectors/surveyanyplace/surveyanyplace_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package surveyanyplace
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "zDbWB2TozuNJj8o56FIB0PVfE7LfjHFd"
- invalidKey = "zDbWB2TozuNJj8o5?FIB0PVfE7LfjHFd"
- validId = "UEN9VEUk4z1oY-pV2CPLZBAnn0rPWY3LCqNv"
- invalidId = "UEN9VEUk4z1oY-pV2C?LZBAnn0rPWY3LCqNv"
- keyword = "surveyanyplace"
-)
-
-func TestSurveyAnyplace_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword surveyanyplace",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/surveybot/surveybot.go b/pkg/detectors/surveybot/surveybot.go
deleted file mode 100644
index 27eddefd05d6..000000000000
--- a/pkg/detectors/surveybot/surveybot.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package surveybot
-
-import (
- "context"
- "encoding/json"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"surveybot"}) + `\b([A-Za-z0-9-]{80})\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{"surveybot"}
-}
-
-type response struct {
- Surveys []interface{} `json:"surveys"`
-}
-
-// FromData will find and optionally verify SurveyBot secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SurveyBot,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://app.surveybot.io/api/v1/surveys?page=1&per_page=20&offset=0", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("X-Api-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- var r response
- if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
- s1.SetVerificationError(err, resMatch)
- continue
- }
- if len(r.Surveys) > 0 {
- s1.Verified = true
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SurveyBot
-}
-
-func (s Scanner) Description() string {
- return "SurveyBot is a service used for conducting surveys. SurveyBot API keys can be used to access and manage surveys."
-}
diff --git a/pkg/detectors/surveybot/surveybot_integration_test.go b/pkg/detectors/surveybot/surveybot_integration_test.go
deleted file mode 100644
index dd567c2c19b3..000000000000
--- a/pkg/detectors/surveybot/surveybot_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package surveybot
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSurveyBot_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SURVEYBOT_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SURVEYBOT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a surveybot secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SurveyBot,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a surveybot secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SurveyBot,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SurveyBot.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SurveyBot.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/surveybot/surveybot_test.go b/pkg/detectors/surveybot/surveybot_test.go
deleted file mode 100644
index c8210aca3e97..000000000000
--- a/pkg/detectors/surveybot/surveybot_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package surveybot
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "ZGPxFPcng3xOumtiY14y8Gtp8IAp5pHlnxOf28l3ThncGCOHdU4o01muxq4UqAJP28s4hfkASbzVY0Qi"
- invalidPattern = "ZGPxFPcng3xOumtiY14y8Gtp8IAp5pHlnxOf28l3?hncGCOHdU4o01muxq4UqAJP28s4hfkASbzVY0Qi"
- keyword = "surveybot"
-)
-
-func TestSurveyBot_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword surveybot",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/surveysparrow/surveysparrow.go b/pkg/detectors/surveysparrow/surveysparrow.go
deleted file mode 100644
index 436e1cefac1a..000000000000
--- a/pkg/detectors/surveysparrow/surveysparrow.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package surveysparrow
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"surveysparrow"}) + `\b([a-zA-Z0-9-_]{88})\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{"surveysparrow"}
-}
-
-// FromData will find and optionally verify SurveySparrow secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_SurveySparrow,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.surveysparrow.com/v1/contacts", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_SurveySparrow
-}
-
-func (s Scanner) Description() string {
- return "SurveySparrow is a survey platform for collecting feedback and data. The detected key can be used to access and manage surveys and responses."
-}
diff --git a/pkg/detectors/surveysparrow/surveysparrow_integration_test.go b/pkg/detectors/surveysparrow/surveysparrow_integration_test.go
deleted file mode 100644
index 607b5a53ff35..000000000000
--- a/pkg/detectors/surveysparrow/surveysparrow_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package surveysparrow
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSurveySparrow_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SURVEYSPARROW_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SURVEYSPARROW_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a surveysparrow secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SurveySparrow,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a surveysparrow secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_SurveySparrow,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("SurveySparrow.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("SurveySparrow.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/surveysparrow/surveysparrow_test.go b/pkg/detectors/surveysparrow/surveysparrow_test.go
deleted file mode 100644
index c82d88b031ee..000000000000
--- a/pkg/detectors/surveysparrow/surveysparrow_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package surveysparrow
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "nRTqAmYY7E8pi6CaSf4JGgvxreYC-DEI6Q9De1EYLilWDuSHGOugKnWYdIz7ifGlFDol4v2hsYH7C9lu5Z63XmwP"
- invalidPattern = "nRTqAmYY7E8pi6CaSf4JGgvxreYC-?EI6Q9De1EYLilWDuSHGOugKnWYdIz7ifGlFDol4v2hsYH7C9lu5Z63XmwP"
- keyword = "surveysparrow"
-)
-
-func TestSurveySparrow_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword surveysparrow",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/survicate/survicate.go b/pkg/detectors/survicate/survicate.go
deleted file mode 100644
index 0dae1e6fc1b3..000000000000
--- a/pkg/detectors/survicate/survicate.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package survicate
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"survicate"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"survicate"}
-}
-
-// FromData will find and optionally verify Survicate secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Survicate,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://data-api.survicate.com/v1/surveys", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Survicate
-}
-
-func (s Scanner) Description() string {
- return "Survicate is a survey platform that allows users to collect feedback and insights from customers. Survicate API keys can be used to access and manage surveys and responses."
-}
diff --git a/pkg/detectors/survicate/survicate_integration_test.go b/pkg/detectors/survicate/survicate_integration_test.go
deleted file mode 100644
index 61db64034c47..000000000000
--- a/pkg/detectors/survicate/survicate_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package survicate
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSurvicate_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SURVICATE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("SURVICATE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a survicate secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Survicate,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a survicate secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Survicate,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Survicate.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Survicate.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/survicate/survicate_test.go b/pkg/detectors/survicate/survicate_test.go
deleted file mode 100644
index f4e2c9370d21..000000000000
--- a/pkg/detectors/survicate/survicate_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package survicate
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "lztej917o59y8x82f311v8d684eb2t6k"
- invalidPattern = "lztej917o59y8x82?311v8d684eb2t6k"
- keyword = "survicate"
-)
-
-func TestSurvicate_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword survicate",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/swell/swell.go b/pkg/detectors/swell/swell.go
deleted file mode 100644
index de4147218c03..000000000000
--- a/pkg/detectors/swell/swell.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package swell
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"swell"}) + `\b([a-zA-Z0-9]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"swell"}) + `\b([a-zA-Z0-9]{6,24})\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{"swell"}
-}
-
-// FromData will find and optionally verify Swell secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- tokenPatMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- userPatMatch := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Swell,
- Raw: []byte(tokenPatMatch),
- }
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.swell.store/products?limit=100", nil)
- if err != nil {
- continue
- }
- req.SetBasicAuth(userPatMatch, tokenPatMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Swell
-}
-
-func (s Scanner) Description() string {
- return "Swell is an eCommerce platform that allows businesses to create and manage online stores. Swell API keys can be used to access and modify store data."
-}
diff --git a/pkg/detectors/swell/swell_integration_test.go b/pkg/detectors/swell/swell_integration_test.go
deleted file mode 100644
index 90edf070291a..000000000000
--- a/pkg/detectors/swell/swell_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package swell
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSwell_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SWELL")
- id := testSecrets.MustGetField("SWELL_ID")
- inactiveSecret := testSecrets.MustGetField("SWELL_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a swell secret %s within swell %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Swell,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a swell secret %s within swell %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Swell,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Swell.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Swell.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/swell/swell_test.go b/pkg/detectors/swell/swell_test.go
deleted file mode 100644
index 00a6ef92cb56..000000000000
--- a/pkg/detectors/swell/swell_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package swell
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "Mkff1bQx72mxhvNdCsudapMP7eery91G"
- invalidKey = "Mkff1bQx72mxhvNd?sudapMP7eery91G"
- validId = "0aOWuIMHYK6F30I1"
- invalidId = "?aOWuIMHYK6F30I?"
- keyword = "swell"
-)
-
-func TestSwell_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword swell",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/swiftype/swiftype.go b/pkg/detectors/swiftype/swiftype.go
deleted file mode 100644
index f3cc5a678aac..000000000000
--- a/pkg/detectors/swiftype/swiftype.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package swiftype
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"swiftype"}) + `\b([a-zA-z-0-9]{6}\_[a-zA-z-0-9]{6}\-[a-zA-z-0-9]{6})\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{"swiftype"}
-}
-
-// FromData will find and optionally verify Swiftype secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Swiftype,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{"auth_token":"` + resMatch + `","q": "gatsby"}`)
- req, err := http.NewRequestWithContext(ctx, "GET", "https://search-api.swiftype.com/api/v1/engines/bookstore/document_types/books/search.json", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Swiftype
-}
-
-func (s Scanner) Description() string {
- return "Swiftype is a search technology that provides powerful search solutions for websites and applications. Swiftype keys can be used to access and manipulate search data."
-}
diff --git a/pkg/detectors/swiftype/swiftype_integration_test.go b/pkg/detectors/swiftype/swiftype_integration_test.go
deleted file mode 100644
index bafbcb78a094..000000000000
--- a/pkg/detectors/swiftype/swiftype_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package swiftype
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestSwiftype_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("SWIFTYPE")
- inactiveSecret := testSecrets.MustGetField("SWIFTYPE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a swiftype secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Swiftype,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a swiftype secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Swiftype,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Swiftype.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Swiftype.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/swiftype/swiftype_test.go b/pkg/detectors/swiftype/swiftype_test.go
deleted file mode 100644
index 3715353e8de6..000000000000
--- a/pkg/detectors/swiftype/swiftype_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package swiftype
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "hJgyi1_y8ohpS-N2vlXE"
- invalidPattern = "?Jgyi1_y8ohpS-N2vlXE"
- keyword = "swiftype"
-)
-
-func TestSwiftype_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword swiftype",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tableau/tableau.go b/pkg/detectors/tableau/tableau.go
deleted file mode 100644
index 7bece3f117cf..000000000000
--- a/pkg/detectors/tableau/tableau.go
+++ /dev/null
@@ -1,263 +0,0 @@
-package tableau
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "maps"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- detectors.EndpointSetter
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.EndpointCustomizer = (*Scanner)(nil)
-
-func (Scanner) CloudEndpoint() string { return "" }
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Simplified token name pattern using PrefixRegex
- tokenNamePat = regexp.MustCompile(detectors.PrefixRegex([]string{"pat", "token", "name", "tableau"}) + `([a-zA-Z][a-zA-Z0-9_-]{2,50})`)
-
- // Pattern for Personal Access Token Secrets
- tokenSecretPat = regexp.MustCompile(`\b([A-Za-z0-9+/]{22}==:[A-Za-z0-9]{32})\b`)
-
- // Simplified Tableau Server URLs pattern
- tableauURLPat = regexp.MustCompile(`\b([a-zA-Z0-9\-]+\.online\.tableau\.com)\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-func (s Scanner) Keywords() []string {
- return []string{
- "tableau",
- "online.tableau.com",
- }
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- // Extract token names, secrets, and URLs separately
- tokenNames := extractTokenNames(dataStr)
- tokenSecrets := extractTokenSecrets(dataStr)
- foundURLs := extractTableauURLs(dataStr)
-
- // If no names or secrets found, return empty results
- if len(tokenNames) == 0 || len(tokenSecrets) == 0 {
- return results, nil
- }
-
- // Use maps to deduplicate endpoints
- var uniqueEndpoints = make(map[string]struct{})
-
- // Add endpoints to the list
- for _, endpoint := range s.Endpoints(foundURLs...) {
- // Remove https:// prefix if present since we add it during verification
- endpoint = strings.TrimPrefix(endpoint, "https://")
- uniqueEndpoints[endpoint] = struct{}{}
- }
-
- // Process each combination of token name, token secret, and endpoint
- for _, tokenName := range tokenNames {
- for _, tokenSecret := range tokenSecrets {
- for endpoint := range uniqueEndpoints {
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Raw: []byte(tokenName),
- RawV2: []byte(fmt.Sprintf("%s:%s:%s", tokenName, tokenSecret, endpoint)),
- ExtraData: make(map[string]string),
- }
-
- if verify {
- client := s.getClient()
- isVerified, extraData, verificationErr := verifyTableauPAT(ctx, client, tokenName, tokenSecret, endpoint)
- result.Verified = isVerified
- maps.Copy(result.ExtraData, extraData)
- result.SetVerificationError(verificationErr, tokenName, tokenSecret, endpoint)
- }
- results = append(results, result)
- }
- }
- }
-
- return results, nil
-}
-
-// extractTokenNames finds all potential token names in the data
-func extractTokenNames(data string) []string {
- var names []string
- // Create a map of false positive terms
- falsePositives := map[detectors.FalsePositive]struct{}{
- detectors.FalsePositive("com"): {},
- }
-
- for _, match := range tokenNamePat.FindAllStringSubmatch(data, -1) {
- if len(match) >= 2 {
- name := strings.TrimSpace(match[1])
- isFalsePositive, _ := detectors.IsKnownFalsePositive(name, falsePositives, false)
- if !isFalsePositive {
- names = append(names, name)
- }
- }
- }
- return names
-}
-
-// extractTokenSecrets finds all potential token secrets in the data
-func extractTokenSecrets(data string) []string {
- var secrets []string
- for _, match := range tokenSecretPat.FindAllStringSubmatch(data, -1) {
- if len(match) >= 2 {
- secret := strings.TrimSpace(match[1])
- secrets = append(secrets, secret)
- }
- }
- return secrets
-}
-
-// extractTableauURLs finds all potential Tableau server URLs in the data
-func extractTableauURLs(data string) []string {
- var urls []string
-
- for _, match := range tableauURLPat.FindAllStringSubmatch(data, -1) {
- if len(match) >= 2 {
- url := strings.TrimSpace(match[1])
- if url != "" {
- urls = append(urls, url)
- }
- }
- }
-
- return urls
-}
-
-// TableauAuthRequest represents the authentication request structure
-type TableauAuthRequest struct {
- Credentials TableauCredentials `json:"credentials"`
-}
-
-type TableauCredentials struct {
- PersonalAccessTokenName string `json:"personalAccessTokenName"`
- PersonalAccessTokenSecret string `json:"personalAccessTokenSecret"`
- Site interface{} `json:"site"`
-}
-
-// TableauAuthResponse represents the authentication response structure
-type TableauAuthResponse struct {
- Credentials struct {
- Site struct {
- ID string `json:"id"`
- ContentURL string `json:"contentUrl"`
- } `json:"site"`
- User struct {
- ID string `json:"id"`
- } `json:"user"`
- Token string `json:"token"`
- } `json:"credentials"`
-}
-
-// verifyTableauPAT verifies a Tableau Personal Access Token by attempting authentication
-func verifyTableauPAT(ctx context.Context, client *http.Client, tokenName, tokenSecret, endpoint string) (bool, map[string]string, error) {
- // Build the verification URL
- verifyURL := fmt.Sprintf("https://%s/api/3.26/auth/signin", endpoint)
-
- // Prepare metadata early - before any potential errors
- extraData := map[string]string{
- "verification_endpoint": verifyURL,
- "verification_method": "tableau_pat_auth",
- "tableau_endpoint": endpoint,
- }
-
- // Rest of your verification logic...
- authReq := TableauAuthRequest{
- Credentials: TableauCredentials{
- PersonalAccessTokenName: tokenName,
- PersonalAccessTokenSecret: tokenSecret,
- Site: map[string]interface{}{},
- },
- }
-
- // Marshal to JSON
- jsonData, err := json.Marshal(authReq)
- if err != nil {
- return false, nil, fmt.Errorf("failed to marshal auth request: %v", err)
- }
-
- // Create HTTP request
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, verifyURL, bytes.NewBuffer(jsonData))
- if err != nil {
- return false, nil, fmt.Errorf("failed to create request: %v", err)
- }
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Accept", "application/json")
-
- // Execute request
- resp, err := client.Do(req)
- if err != nil {
- // Check if it's a DNS/network error
- if strings.Contains(err.Error(), "no such host") ||
- strings.Contains(err.Error(), "dial tcp") ||
- strings.Contains(err.Error(), "connection refused") {
- extraData["network_error"] = "true"
- return false, extraData, nil // No error, just invalid endpoint
- }
- return false, nil, fmt.Errorf("request failed: %v", err)
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- // Read the response
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, nil, fmt.Errorf("failed to read response body: %v", err)
- }
-
- // Status code handling...
- switch resp.StatusCode {
- case http.StatusOK:
- var authResp TableauAuthResponse
- if err := json.Unmarshal(bodyBytes, &authResp); err != nil {
- return true, extraData, err
- }
- return true, extraData, nil
-
- case http.StatusUnauthorized, http.StatusBadRequest, http.StatusForbidden:
- return false, extraData, nil
-
- default:
- return false, extraData, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TableauPersonalAccessToken
-}
-
-func (s Scanner) Description() string {
- return "Tableau is a data visualization and business intelligence platform. Personal Access Tokens (PATs) provide programmatic access to Tableau Server/Online APIs and can be used to authenticate applications and automate workflows."
-}
diff --git a/pkg/detectors/tableau/tableau_integration_test.go b/pkg/detectors/tableau/tableau_integration_test.go
deleted file mode 100644
index c0ddd495b183..000000000000
--- a/pkg/detectors/tableau/tableau_integration_test.go
+++ /dev/null
@@ -1,266 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tableau
-
-import (
- "context"
- "fmt"
-
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTableau_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- tokenName := testSecrets.MustGetField("TABLEAU_TOKEN_NAME")
- tokenSecret := testSecrets.MustGetField("TABLEAU_TOKEN_SECRET")
- inactiveTokenName := testSecrets.MustGetField("TABLEAU_INACTIVE_TOKEN_NAME")
- inactiveTokenSecret := testSecrets.MustGetField("TABLEAU_INACTIVE_TOKEN_SECRET")
- tableauURL := testSecrets.MustGetField("TABLEAU_VALID_POD_NAME")
- invalidURL := testSecrets.MustGetField("TABLEAU_INVALID_POD_NAME")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified with URL",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("token=%s\nsecret=%s\nserver=%s", tokenName, tokenSecret, tableauURL)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: true,
- ExtraData: map[string]string{
- "token_name": tokenName,
- "token_secret": tokenSecret,
- "endpoint": tableauURL,
- "credential_type": "personal_access_token",
- "verification_endpoint": "https://" + tableauURL + "/api/3.26/auth/signin",
- "http_status": "200",
- "verification_method": "tableau_pat_auth",
- "verification_status": "valid",
- "auth_token_received": "true",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(500, "Internal Server Error")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("name = '%s'\n secret = '%s'\nserver = '%s'", tokenName, tokenSecret, tableauURL)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: false,
- ExtraData: map[string]string{
- "token_name": tokenName,
- "token_secret": tokenSecret,
- "endpoint": tableauURL,
- "credential_type": "personal_access_token",
- "verification_endpoint": "https://" + tableauURL + "/api/3.26/auth/signin",
- "http_status": "500",
- "verification_method": "tableau_pat_auth",
- "verification_status": "error",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, unverified with invalid URL",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("name = '%s'\nsecret = '%s'\nserver = '%s'", tokenName, tokenSecret, invalidURL)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: false,
- ExtraData: map[string]string{
- "token_name": tokenName,
- "token_secret": tokenSecret,
- "endpoint": invalidURL,
- "credential_type": "personal_access_token",
- "verification_endpoint": "https://" + invalidURL + "/api/3.26/auth/signin",
- "http_status": "404", // or whatever status invalid URL returns
- "verification_method": "tableau_pat_auth",
- "verification_status": "error",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified with inactive token",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("name = '%s'\n secret = '%s'\nserver = '%s'", inactiveTokenName, inactiveTokenSecret, tableauURL)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: false,
- ExtraData: map[string]string{
- "token_name": inactiveTokenName,
- "token_secret": inactiveTokenSecret,
- "endpoint": tableauURL,
- "credential_type": "personal_access_token",
- "verification_endpoint": "https://" + tableauURL + "/api/3.26/auth/signin",
- "http_status": "401",
- "verification_method": "tableau_pat_auth",
- "verification_status": "invalid",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the tableau secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- {
- name: "found, unverified - malformed token",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("tableau pat_name = 'TestToken'\ntableau pat_secret = 'malformed_secret_format'"),
- verify: true,
- },
- want: nil, // Should not find due to invalid secret format
- wantErr: false,
- },
- {
- name: "found multiple, mixed verification results with URLs",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf(`
- name1 = '%s'
- name2 = '%s'
- secret = '%s'
- secret2 = '%s'
- server1 = '%s'
- server2 = '%s'
- `, tokenName, inactiveTokenName, tokenSecret, inactiveTokenSecret, tableauURL, invalidURL)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: true, // tokenName + tokenSecret + valid URL
- },
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: false, // tokenName + tokenSecret + invalid URL
- },
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: false, // tokenName + inactiveTokenSecret + valid URL
- },
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: false, // tokenName + inactiveTokenSecret + invalid URL
- },
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: false, // inactiveTokenName + tokenSecret + valid URL
- },
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: false, // inactiveTokenName + tokenSecret + invalid URL
- },
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: false, // inactiveTokenName + inactiveTokenSecret + valid URL
- },
- {
- DetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,
- Verified: false, // inactiveTokenName + inactiveTokenSecret + invalid URL
- },
- },
- wantErr: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Enable found endpoints for tests that need URL detection
- scanner := tt.s
- scanner.UseFoundEndpoints(true)
-
- got, err := scanner.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tableau.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Check that we got the expected number of results
- if len(got) != len(tt.want) {
- t.Errorf("Tableau.FromData() got %d results, want %d", len(got), len(tt.want))
- }
-
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Errorf("Tableau.FromData() verificationError = %v, wantVerificationErr %v", got[i].VerificationError(), tt.wantVerificationErr)
- }
- }
-
- ignoreOpts := []cmp.Option{
- cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "ExtraData"),
- cmpopts.IgnoreUnexported(detectors.Result{}),
- }
-
- if diff := cmp.Diff(got, tt.want, ignoreOpts...); diff != "" {
- t.Errorf("Tableau.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tableau/tableau_test.go b/pkg/detectors/tableau/tableau_test.go
deleted file mode 100644
index 2406a218c49e..000000000000
--- a/pkg/detectors/tableau/tableau_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-package tableau
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPATName = "test-token-6"
- validPATSecret = "YMqNfVWiTSa0QgpoJ9GpCw==:KTamjuKrXF5gVETjIJafBBvP8Ctj5aJ3"
- invalidPATSecret = "invalid-secret-format"
- // Tableau Online endpoints for testing
- validTableauURL = "prod-ansouthgest-a.online.tableau.com"
- valid2ndTableauURL = "prod.online.tableau.com"
- invalidTableauURL = "prod.tabeau.com"
-)
-
-func TestTableau_Pattern(t *testing.T) {
- d := Scanner{}
- d.UseFoundEndpoints(true) // Enable found endpoints for tests
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- description string
- }{
- {
- name: "tableau_prefix_with_token_name",
- input: fmt.Sprintf("token=%s\nsecret=%s\nserver=%s", validPATName, validPATSecret, validTableauURL),
- want: []string{fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL)},
- description: "Tests tableau prefix directly followed by token name",
- },
- {
- name: "pat_prefix_with_name",
- input: fmt.Sprintf("pat %s\n%s\nurl=%s", validPATName, validPATSecret, validTableauURL),
- want: []string{fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL)},
- description: "Tests pat prefix triggering detection",
- },
- {
- name: "name_prefix_with_token",
- input: fmt.Sprintf("name %s\n%s\nurl=%s", validPATName, validPATSecret, validTableauURL),
- want: []string{fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL)},
- description: "Tests name prefix triggering detection",
- },
- {
- name: "single_token_multiple_urls",
- input: fmt.Sprintf("token %s\n%s\nserver1=%s\nserver2=%s", validPATName, validPATSecret, validTableauURL, valid2ndTableauURL),
- want: []string{
- fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL),
- fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, valid2ndTableauURL),
- },
- description: "Tests single token with multiple URLs",
- },
- {
- name: "invalid_secret_format",
- input: fmt.Sprintf("tableau %s\n%s\nserver=%s", validPATName, invalidPATSecret, validTableauURL),
- want: []string{},
- description: "Tests that invalid secret format is not detected",
- },
- {
- name: "invalid_tableau_url",
- input: fmt.Sprintf("tableau %s\n%s\nserver=%s", validPATName, validPATSecret, invalidTableauURL),
- want: []string{},
- description: "Tests that invalid Tableau URL is not detected when useFoundEndpoints is true",
- },
- {
- name: "missing_token_name",
- input: fmt.Sprintf("\n%s\nserver=%s", validPATSecret, validTableauURL),
- want: []string{},
- description: "Tests that missing token name produces no results",
- },
- {
- name: "missing_secret",
- input: fmt.Sprintf("tableau %s\nserver=%s", validPATName, validTableauURL),
- want: []string{},
- description: "Tests that missing secret produces no results",
- },
- {
- name: "no_tableau_keywords",
- input: "username=test\npassword=secret123\nhost=example.com",
- want: []string{},
- description: "Tests that non-Tableau config produces no results",
- },
- {
- name: "token_name_with_whitespace",
- input: fmt.Sprintf("name %s\n%s\nserver=%s", validPATName, validPATSecret, validTableauURL),
- want: []string{fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL)},
- description: "Tests token name with extra whitespace",
- },
- {
- name: "pat_with_single_quotes",
- input: fmt.Sprintf("pat '%s'\n%s\nserver=%s", validPATName, validPATSecret, validTableauURL),
- want: []string{fmt.Sprintf("%s:%s:%s", validPATName, validPATSecret, validTableauURL)},
- description: "Tests token name with single quotes",
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(test.want) > 0 {
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched in input: %s", d.Keywords(), test.input)
- return
- }
- }
-
- if len(matchedDetectors) == 0 && len(test.want) == 0 {
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("FromData error: %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- t.Errorf("expected %d results, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- actual[string(r.RawV2)] = struct{}{}
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tailscale/tailscale.go b/pkg/detectors/tailscale/tailscale.go
deleted file mode 100644
index 4d40e8c63c84..000000000000
--- a/pkg/detectors/tailscale/tailscale.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package tailscale
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "net/url"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\btskey-[a-z]+-[0-9A-Za-z_]+-[0-9A-Za-z_]+\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{"tskey-", "tskey-api-", "tskey-oauth-"}
-}
-
-// FromData will find and optionally verify Tailscaleapi secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[0])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Tailscale,
- Raw: []byte(resMatch),
- }
-
- if verify {
- const u = "https://api.tailscale.com/api/v2/secret-scanning/verify"
- vals := url.Values{"key": []string{resMatch}}
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, strings.NewReader(vals.Encode()))
- if err != nil {
- continue
- }
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- switch res.StatusCode {
- case http.StatusNoContent:
- s1.Verified = true
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- default:
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Tailscale
-}
-
-func (s Scanner) Description() string {
- return "Tailscale is a zero-config VPN for building secure networks. Tailscale API keys can be used to authenticate and interact with the Tailscale API."
-}
diff --git a/pkg/detectors/tailscale/tailscale_integration_test.go b/pkg/detectors/tailscale/tailscale_integration_test.go
deleted file mode 100644
index 28ee3ba5d8ad..000000000000
--- a/pkg/detectors/tailscale/tailscale_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tailscale
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTailscaleapi_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TAILSCALEAPI")
- inactiveSecret := testSecrets.MustGetField("TAILSCALEAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tailscaleapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tailscale,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tailscaleapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tailscale,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tailscaleapi.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Tailscaleapi.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tailscale/tailscale_test.go b/pkg/detectors/tailscale/tailscale_test.go
deleted file mode 100644
index fccf1f89aed7..000000000000
--- a/pkg/detectors/tailscale/tailscale_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package tailscale
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "tskey-rtzgayeq-RLL0xBAIXBhkYRhir0gmxHNoARkj0CqpNTj_xM2Zpm4lDxEIGsYbwO_kzlNSwrQrAOL4yacZGlBfj37e3WRRlmYfKtrgC-xmk0NNQFLQGCTPwcwQT7d6YipCXS1ScCVL8So"
- invalidPattern = "tskey-rtzgayeq-RLL0xBAIXBhkYRhir0gmxH?oARkj0CqpNTj_xM2Zpm4lDxEIGsYbwO_kzlNSwrQrAOL4yacZGlBfj37e3WRRlmYfKtrgC-xmk0NNQFLQGCTPwcwQT7d6YipCXS1ScCVL8So"
- keyword = "tailscale"
-)
-
-func TestTailscaleapi_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tailscale",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tallyfy/tallyfy.go b/pkg/detectors/tallyfy/tallyfy.go
deleted file mode 100644
index d2e4c687dfdc..000000000000
--- a/pkg/detectors/tallyfy/tallyfy.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package tallyfy
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tallyfy"}) + `\b([0-9A-Za-z]{36}\.[0-9A-Za-z]{264}\.[0-9A-Za-z\-\_]{683})\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{"tallyfy"}
-}
-
-// FromData will find and optionally verify Tallyfy secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Tallyfy,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.tallyfy.com/me", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Tallyfy
-}
-
-func (s Scanner) Description() string {
- return "Tallyfy is a process management and workflow automation platform. Tallyfy API keys can be used to access and automate workflows and processes within the platform."
-}
diff --git a/pkg/detectors/tallyfy/tallyfy_integration_test.go b/pkg/detectors/tallyfy/tallyfy_integration_test.go
deleted file mode 100644
index 82cdc35d020d..000000000000
--- a/pkg/detectors/tallyfy/tallyfy_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tallyfy
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTallyfy_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TALLYFY")
- inactiveSecret := testSecrets.MustGetField("TALLYFY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tallyfy secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tallyfy,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tallyfy secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tallyfy,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tallyfy.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Tallyfy.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/tallyfy/tallyfy_test.go b/pkg/detectors/tallyfy/tallyfy_test.go
deleted file mode 100644
index 3065da9edb25..000000000000
--- a/pkg/detectors/tallyfy/tallyfy_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tallyfy
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "K6azzL9elP7qKOQIyU2PF68Ej1WiZWCIQBUY.nYqWoaP99FfEwzxQOuMLbkFvyu4i4AM74Dk55iokNXhD2YeyFuwze060X8NNXN1WqiVgavVLTuL9S45Q8hpa5yB9rVREeWrR3YhNclTRKL1vnW1f4BJZIMIOIPNFtN70ZXwIZEdDXzvZZtBLFA36R1WlKKKKd1NV7Rnc1RLCk9trhYwFsngcRFVPvqwLbPIv3hsfia31IDFDHXAdHuWXyDb0dA0vvOQqxmYn0kUEX13rEdFzBnZ7rJL861B9vCYkbBJSgujp.EV15KF8CXF3GmQ5NgXo_8KMkmLc55Z6v3JBVstk381Bpr53q3tqISdfjj3zNj1bRFk6KXNrjUzLMkZjVBbpme5-dAkJFcs5r1jmHXkrRmE0xLk25TUX8TukVdzCjWxYogQJopcXHfw_uqpF8JJ1FomLhpRu8Ag0RrRaO-IfF0Nz-SbQcIOtEJrvbfzVF3LVDPU5ID8bi3cz3qVuS30TxR-0fLaQHnRq0POGYlsy-OEZI078Wj0g3diAr8Yy6SGOkT-CB0p175aYkhUmNGhWpsGMyjASr-MXT9i2HPUPoI50X-Xw6HsQrqydLqYQg-uA2FDTpqza3IZMcyYj-4AeFWgzj3fSgyqM-IK47Cf52hzrW4YPmI9Lz-ooGPbd1ZAJXVLDrNtsG9U4VetIxPKpz4RgeVnGwy2NW53U1L7sMixwewhBvL8WL-T5FEyLt8-q-6h_ubaOxhVKjEzwIkunAzLYfCLYCj0MaPvTfn2mSbJ5r_5Mc6Mx-skqUt_yJhKsyYoXQOFni0awoPIf_xOEpvjmUELr2ZyPB-EqiFXB9zRC4oYO2QULWQYocd0cCKHsRL_ulfZ9qI1XR4Wl04DumNuJHdXodiSWXMwUOgsNf-vMs-2IHoVahWkihTSV6FyDNAitSLSEGNK5x_egOOY5tJWyvZpU8s80Rw7lF6Y-8C5f"
- invalidPattern = "K?azzL9elP7qKOQIyU2PF68Ej1WiZWCIQBUY.nYqWoaP99FfEwzxQOuMLbkFvyu4i4AM74Dk55iokNXhD2YeyFuwze060X8NNXN1WqiVgavVLTuL9S45Q8hpa5yB9rVREeWrR3YhNclTRKL1vnW1f4BJZIMIOIPNFtN70ZXwIZEdDXzvZZtBLFA36R1WlKKKKd1NV7Rnc1RLCk9trhYwFsngcRFVPvqwLbPIv3hsfia31IDFDHXAdHuWXyDb0dA0vvOQqxmYn0kUEX13rEdFzBnZ7rJL861B9vCYkbBJSgujp.EV15KF8CXF3GmQ5NgXo_8KMkmLc55Z6v3JBVstk381Bpr53q3tqISdfjj3zNj1bRFk6KXNrjUzLMkZjVBbpme5-dAkJFcs5r1jmHXkrRmE0xLk25TUX8TukVdzCjWxYogQJopcXHfw_uqpF8JJ1FomLhpRu8Ag0RrRaO-IfF0Nz-SbQcIOtEJrvbfzVF3LVDPU5ID8bi3cz3qVuS30TxR-0fLaQHnRq0POGYlsy-OEZI078Wj0g3diAr8Yy6SGOkT-CB0p175aYkhUmNGhWpsGMyjASr-MXT9i2HPUPoI50X-Xw6HsQrqydLqYQg-uA2FDTpqza3IZMcyYj-4AeFWgzj3fSgyqM-IK47Cf52hzrW4YPmI9Lz-ooGPbd1ZAJXVLDrNtsG9U4VetIxPKpz4RgeVnGwy2NW53U1L7sMixwewhBvL8WL-T5FEyLt8-q-6h_ubaOxhVKjEzwIkunAzLYfCLYCj0MaPvTfn2mSbJ5r_5Mc6Mx-skqUt_yJhKsyYoXQOFni0awoPIf_xOEpvjmUELr2ZyPB-EqiFXB9zRC4oYO2QULWQYocd0cCKHsRL_ulfZ9qI1XR4Wl04DumNuJHdXodiSWXMwUOgsNf-vMs-2IHoVahWkihTSV6FyDNAitSLSEGNK5x_egOOY5tJWyvZpU8s80Rw7lF6Y-8C5f"
- keyword = "tallyfy"
-)
-
-func TestTallyfy_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tallyfy",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tatumio/tatumio.go b/pkg/detectors/tatumio/tatumio.go
deleted file mode 100644
index 31930b3e6d2f..000000000000
--- a/pkg/detectors/tatumio/tatumio.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package tatumio
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tatum"}) + `\b([0-9a-z-]{36})\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{"tatum"}
-}
-
-// FromData will find and optionally verify TatumIO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TatumIO,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api-eu1.tatum.io/v3/ledger/account?pageSize=10&offset=0&accountCode=AC_1011_B", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.tatumio+json; version=3")
- req.Header.Add("x-api-key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TatumIO
-}
-
-func (s Scanner) Description() string {
- return "Tatum is a blockchain infrastructure platform. Tatum API keys can be used to access and interact with blockchain services provided by Tatum."
-}
diff --git a/pkg/detectors/tatumio/tatumio_integration_test.go b/pkg/detectors/tatumio/tatumio_integration_test.go
deleted file mode 100644
index 885ed5268f9b..000000000000
--- a/pkg/detectors/tatumio/tatumio_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tatumio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTatumIO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TATUMIO")
- inactiveSecret := testSecrets.MustGetField("TATUMIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tatumio secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TatumIO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tatumio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TatumIO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TatumIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TatumIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tatumio/tatumio_test.go b/pkg/detectors/tatumio/tatumio_test.go
deleted file mode 100644
index 926c8fe4663b..000000000000
--- a/pkg/detectors/tatumio/tatumio_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tatumio
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "xu4iyzk6fa6ld8nz9ayms4yq3wbh48qmiu8k"
- invalidPattern = "xu4i?zk6fa6ld8nz9ayms4yq3wbh48qmiu8k"
- keyword = "tatumio"
-)
-
-func TestTatumIO_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tatumio",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/taxjar/taxjar.go b/pkg/detectors/taxjar/taxjar.go
deleted file mode 100644
index 5dc44105a358..000000000000
--- a/pkg/detectors/taxjar/taxjar.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package taxjar
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"taxjar"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"taxjar"}
-}
-
-// FromData will find and optionally verify Taxjar secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Taxjar,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.taxjar.com/v2/categories", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Taxjar
-}
-
-func (s Scanner) Description() string {
- return "Taxjar is a sales tax API that allows businesses to automate sales tax calculations, reporting, and filings. Taxjar API keys can be used to access and manage these services."
-}
diff --git a/pkg/detectors/taxjar/taxjar_integration_test.go b/pkg/detectors/taxjar/taxjar_integration_test.go
deleted file mode 100644
index 1eb5ea716137..000000000000
--- a/pkg/detectors/taxjar/taxjar_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package taxjar
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTaxjar_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TAXJAR_TOKEN")
- inactiveSecret := testSecrets.MustGetField("TAXJAR_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a taxjar secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Taxjar,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a taxjar secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Taxjar,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Taxjar.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Taxjar.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/taxjar/taxjar_test.go b/pkg/detectors/taxjar/taxjar_test.go
deleted file mode 100644
index efd34e121562..000000000000
--- a/pkg/detectors/taxjar/taxjar_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package taxjar
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "2jnsyu2ebcnaemws9b2rmh6w6e12i8j7"
- invalidPattern = "2jnsyu2ebcnaemws?b2rmh6w6e12i8j7"
- keyword = "taxjar"
-)
-
-func TestTaxjar_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword taxjar",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/teamgate/teamgate.go b/pkg/detectors/teamgate/teamgate.go
deleted file mode 100644
index b8793411ef2e..000000000000
--- a/pkg/detectors/teamgate/teamgate.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package teamgate
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- tokenPat = regexp.MustCompile(detectors.PrefixRegex([]string{"teamgate"}) + `\b([a-z0-9]{40})\b`)
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"teamgate"}) + `\b([a-zA-Z0-9]{80})\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{"teamgate"}
-}
-
-// FromData will find and optionally verify Teamgate secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := tokenPat.FindAllStringSubmatch(dataStr, -1)
- keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, keyMatch := range keyMatches {
-
- resKeyMatch := strings.TrimSpace(keyMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Teamgate,
- Raw: []byte(resMatch),
- }
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.teamgate.com/v4/users", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-Auth-Token", resMatch)
- req.Header.Add("X-App-Key", resKeyMatch)
-
- res, err := client.Do(req)
-
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Teamgate
-}
-
-func (s Scanner) Description() string {
- return "Teamgate is a sales CRM platform. Teamgate tokens and keys can be used to access and manage sales data and CRM functionalities."
-}
diff --git a/pkg/detectors/teamgate/teamgate_integration_test.go b/pkg/detectors/teamgate/teamgate_integration_test.go
deleted file mode 100644
index 8ff2b5e7d61e..000000000000
--- a/pkg/detectors/teamgate/teamgate_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package teamgate
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTeamgate_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TEAMGATE")
- key := testSecrets.MustGetField("TEAMGATE_KEY")
- inactiveSecret := testSecrets.MustGetField("TEAMGATE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a teamgate secret %s within teamgate %s", secret, key)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Teamgate,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a teamgate secret %s within teamgate %s but not valid", inactiveSecret, key)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Teamgate,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Teamgate.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Teamgate.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/teamgate/teamgate_test.go b/pkg/detectors/teamgate/teamgate_test.go
deleted file mode 100644
index a46231cefca9..000000000000
--- a/pkg/detectors/teamgate/teamgate_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package teamgate
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validToken = "78fohntospcdns4n7zokz7zr134vn7ua7io7ehp1"
- invalidToken = "78fohntospcdns4n7?okz7zr134vn7ua7io7ehp1"
- validKey = "AdPZhlbr8bIaIUomTizYvauT2HMNUfm6oK4Aft8JICXKvdKbHOEeRVLPycmGLi60QBksu5tPvD8X4ciX"
- invalidKey = "AdPZhlbr8b?aIUomTizYvauT2HMNUfm6oK4Aft8JICXKvdKbHOEeRVLPycmGLi60QBksu5tPvD8X4ciX"
- keyword = "teamgate"
-)
-
-func TestTeamgate_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword teamgate",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validToken, keyword, validKey),
- want: []string{validToken},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidToken, keyword, invalidKey),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/teamworkcrm/teamworkcrm.go b/pkg/detectors/teamworkcrm/teamworkcrm.go
deleted file mode 100644
index 9df53b0bf876..000000000000
--- a/pkg/detectors/teamworkcrm/teamworkcrm.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package teamworkcrm
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"teamwork", "teamworkcrm"}) + `\b(tkn\.v1_[0-9A-Za-z]{71}=[ \r\n]{1})`)
-)
-
-// 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{"teamwork", "teamworkcrm"}
-}
-
-// FromData will find and optionally verify TeamworkCRM secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TeamworkCRM,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://example.teamwork.com/crm/api/v2/users.json", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TeamworkCRM
-}
-
-func (s Scanner) Description() string {
- return "TeamworkCRM is a customer relationship management tool that helps teams manage their sales pipeline and customer interactions. TeamworkCRM tokens can be used to access and manage CRM data and functionalities."
-}
diff --git a/pkg/detectors/teamworkcrm/teamworkcrm_integration_test.go b/pkg/detectors/teamworkcrm/teamworkcrm_integration_test.go
deleted file mode 100644
index 1c55c09d9324..000000000000
--- a/pkg/detectors/teamworkcrm/teamworkcrm_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package teamworkcrm
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTeamworkCRM_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TEAMWORKCRM")
- inactiveSecret := testSecrets.MustGetField("TEAMWORKCRM_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a teamworkcrm secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TeamworkCRM,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a teamworkcrm secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TeamworkCRM,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TeamworkCRM.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TeamworkCRM.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/teamworkcrm/teamworkcrm_test.go b/pkg/detectors/teamworkcrm/teamworkcrm_test.go
deleted file mode 100644
index a0c3db8f547c..000000000000
--- a/pkg/detectors/teamworkcrm/teamworkcrm_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package teamworkcrm
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "tkn.v1_LggER0OG5HCo5DCy1FAHXVJiNKat8RbSygBGt0a5dbZlMN1hwlgSXx2ZzfFg4Ax98BbIIlH="
- invalidPattern = "tkn.v1_L?gER0OG5HCo5DCy1FAHXVJiNKat8RbSygBGt0a5dbZlMN1hwlgSXx2ZzfFg4Ax98BbIIlH="
- keyword = "teamworkcrm"
-)
-
-func TestTeamworkCRM_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword teamworkcrm",
- input: fmt.Sprintf("%s token = '%s '", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s ' | '%s '", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s '", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/teamworkdesk/teamworkdesk.go b/pkg/detectors/teamworkdesk/teamworkdesk.go
deleted file mode 100644
index cae2900bb7aa..000000000000
--- a/pkg/detectors/teamworkdesk/teamworkdesk.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package teamworkdesk
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"teamwork", "teamworkdesk"}) + `\b(tkn\.v1_[0-9A-Za-z]{71}=[ \r\n]{1})`)
-)
-
-// 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{"teamwork", "teamworkdesk"}
-}
-
-// FromData will find and optionally verify TeamworkDesk secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TeamworkDesk,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://example.teamwork.com/desk/api/v2/me.json", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TeamworkDesk
-}
-
-func (s Scanner) Description() string {
- return "TeamworkDesk is a helpdesk software that allows teams to manage customer communications. TeamworkDesk tokens can be used to access and manage customer support tickets and other related data."
-}
diff --git a/pkg/detectors/teamworkdesk/teamworkdesk_integration_test.go b/pkg/detectors/teamworkdesk/teamworkdesk_integration_test.go
deleted file mode 100644
index bf03fed6fa42..000000000000
--- a/pkg/detectors/teamworkdesk/teamworkdesk_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package teamworkdesk
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTeamworkDesk_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TEAMWORKDESK")
- inactiveSecret := testSecrets.MustGetField("TEAMWORKDESK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a teamworkdesk secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TeamworkDesk,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a teamworkdesk secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TeamworkDesk,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TeamworkDesk.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TeamworkDesk.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/teamworkdesk/teamworkdesk_test.go b/pkg/detectors/teamworkdesk/teamworkdesk_test.go
deleted file mode 100644
index 1c49df320f22..000000000000
--- a/pkg/detectors/teamworkdesk/teamworkdesk_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package teamworkdesk
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "tkn.v1_PQD5xBDqpPQHJjX1CFtPaM1xgf0vyrmVkpUtN0aMpkBjBnG8Ifg3ohgpd4w1gG1Otaf0O1v="
- invalidPattern = "tkn.v1_PQD5xBDq?PQHJjX1CFtPaM1xgf0vyrmVkpUtN0aMpkBjBnG8Ifg3ohgpd4w1gG1Otaf0O1v="
- keyword = "teamworkdesk"
-)
-
-func TestTeamworkDesk_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword teamworkdesk",
- input: fmt.Sprintf("%s token = '%s '", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s ' | '%s '", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s '", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/teamworkspaces/teamworkspaces.go b/pkg/detectors/teamworkspaces/teamworkspaces.go
deleted file mode 100644
index d6010582c01f..000000000000
--- a/pkg/detectors/teamworkspaces/teamworkspaces.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package teamworkspaces
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"teamwork", "teamworkspaces"}) + `\b(tkn\.v1_[0-9A-Za-z]{71}=[ \r\n]{1})`)
-)
-
-// 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{"teamwork", "teamworkspaces"}
-}
-
-// FromData will find and optionally verify TeamworkSpaces secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TeamworkSpaces,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://example.teamwork.com/spaces/api/v1/users.json", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TeamworkSpaces
-}
-
-func (s Scanner) Description() string {
- return "TeamworkSpaces is a collaboration tool for teams to share documents and information. The detected key can be used to access and modify spaces and documents within TeamworkSpaces."
-}
diff --git a/pkg/detectors/teamworkspaces/teamworkspaces_integration_test.go b/pkg/detectors/teamworkspaces/teamworkspaces_integration_test.go
deleted file mode 100644
index ea693bad9f45..000000000000
--- a/pkg/detectors/teamworkspaces/teamworkspaces_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package teamworkspaces
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTeamworkSpaces_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TEAMWORKSPACES")
- inactiveSecret := testSecrets.MustGetField("TEAMWORKSPACES_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a teamworkspaces secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TeamworkSpaces,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a teamworkspaces secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TeamworkSpaces,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TeamworkSpaces.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TeamworkSpaces.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/teamworkspaces/teamworkspaces_test.go b/pkg/detectors/teamworkspaces/teamworkspaces_test.go
deleted file mode 100644
index 14b226d69a93..000000000000
--- a/pkg/detectors/teamworkspaces/teamworkspaces_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package teamworkspaces
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "tkn.v1_O7NGF5FYyhV8NYI7vKc5ulE5pQkRKghGRW1o96i3PixggnAhs0IprhOJK2jMfSF8bCx2qaM="
- invalidPattern = "tkn.v1_O7NGF5FYyhV8NYI7vKc5ulE5pQkRKghGR?1o96i3PixggnAhs0IprhOJK2jMfSF8bCx2qaM="
- keyword = "teamworkspaces"
-)
-
-func TestTeamworkSpaces_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword teamworkspaces",
- input: fmt.Sprintf("%s token = '%s '", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s ' | '%s '", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s '", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/technicalanalysisapi/technicalanalysisapi.go b/pkg/detectors/technicalanalysisapi/technicalanalysisapi.go
deleted file mode 100644
index 5059f9bb64f3..000000000000
--- a/pkg/detectors/technicalanalysisapi/technicalanalysisapi.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package technicalanalysisapi
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
- "time"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"technicalanalysisapi"}) + `\b([A-Z0-9]{48})\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{"technicalanalysisapi"}
-}
-
-// FromData will find and optionally verify TechnicalAnalysisApi secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TechnicalAnalysisApi,
- Raw: []byte(resMatch),
- }
-
- if verify {
- timeout := 10 * time.Second
- client.Timeout = timeout
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://technical-analysis-api.com/api/v1/analysis/BTC?apiKey=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TechnicalAnalysisApi
-}
-
-func (s Scanner) Description() string {
- return "TechnicalAnalysisApi is a service used for technical analysis of financial markets. The API key can be used to access and retrieve market analysis data."
-}
diff --git a/pkg/detectors/technicalanalysisapi/technicalanalysisapi_integration_test.go b/pkg/detectors/technicalanalysisapi/technicalanalysisapi_integration_test.go
deleted file mode 100644
index b3ca4b3cb613..000000000000
--- a/pkg/detectors/technicalanalysisapi/technicalanalysisapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package technicalanalysisapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTechnicalAnalysisApi_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TECHNICALANALYSISAPI")
- inactiveSecret := testSecrets.MustGetField("TECHNICALANALYSISAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a technicalanalysisapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TechnicalAnalysisApi,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a technicalanalysisapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TechnicalAnalysisApi,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TechnicalAnalysisApi.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TechnicalAnalysisApi.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/technicalanalysisapi/technicalanalysisapi_test.go b/pkg/detectors/technicalanalysisapi/technicalanalysisapi_test.go
deleted file mode 100644
index 52fab107c428..000000000000
--- a/pkg/detectors/technicalanalysisapi/technicalanalysisapi_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package technicalanalysisapi
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "UNVGPP73JF6PMNU362Q6OEDI2AS6E24BMSAFVAPIXW7XI5O8"
- invalidPattern = "UNVGPP73JF6PMNU36?Q6OEDI2AS6E24BMSAFVAPIXW7XI5O8"
- keyword = "technicalanalysisapi"
-)
-
-func TestTechnicalAnalysisApi_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword technicalanalysisapi",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tefter/tefter.go b/pkg/detectors/tefter/tefter.go
deleted file mode 100644
index bfbea4e539b2..000000000000
--- a/pkg/detectors/tefter/tefter.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package tefter
-
-import (
- "context"
- "encoding/json"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tefter"}) + `\b([0-9a-zA-Z]{20})\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{"tefter"}
-}
-
-// FromData will find and optionally verify Tefter secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Tefter,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "POST", "https://www.tefter.io/api/bookmarks?url=google.com", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-User-Token", resMatch)
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- validResponse := json.Valid(bodyBytes)
-
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 && validResponse {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Tefter
-}
-
-func (s Scanner) Description() string {
- return "Tefter is a bookmarking service. Tefter API keys can be used to access and manage bookmarks."
-}
diff --git a/pkg/detectors/tefter/tefter_integration_test.go b/pkg/detectors/tefter/tefter_integration_test.go
deleted file mode 100644
index 607e8e421364..000000000000
--- a/pkg/detectors/tefter/tefter_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tefter
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTefter_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TEFTER")
- inactiveSecret := testSecrets.MustGetField("TEFTER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tefter secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tefter,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tefter secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tefter,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tefter.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Tefter.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tefter/tefter_test.go b/pkg/detectors/tefter/tefter_test.go
deleted file mode 100644
index f3af28633856..000000000000
--- a/pkg/detectors/tefter/tefter_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tefter
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "gnKLZ6PNGylpRrrJjiWe"
- invalidPattern = "gnKLZ6PNGy?pRrrJjiWe"
- keyword = "tefter"
-)
-
-func TestTefter_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tefter",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/telegrambottoken/telegrambottoken.go b/pkg/detectors/telegrambottoken/telegrambottoken.go
deleted file mode 100644
index 3f9e2007b294..000000000000
--- a/pkg/detectors/telegrambottoken/telegrambottoken.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package telegrambottoken
-
-import (
- "context"
- "encoding/json"
-
- "net/http"
- "strings"
-
- // "fmt"
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // https://core.telegram.org/bots#6-botfather
- // thanks https://stackoverflow.com/questions/61868770/tegram-bot-api-token-format
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"telegram", "tgram://"}) + `\b([0-9]{8,10}:[a-zA-Z0-9_-]{35})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- // Apprise uses the `tgram://` url scheme.
- // https://github.com/caronc/apprise/wiki/Notify_telegram
- return []string{"telegram", "tgram"}
-}
-
-// FromData will find and optionally verify TelegramBotToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- key := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TelegramBotToken,
- Raw: []byte(key),
- }
-
- if verify {
- // https://core.telegram.org/bots/api#getme
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.telegram.org/bot"+key+"/getMe", nil)
- if err != nil {
- continue
- }
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
-
- apiRes := apiResponse{}
- err := json.NewDecoder(res.Body).Decode(&apiRes)
- if err == nil && apiRes.Ok {
- s1.ExtraData = map[string]string{
- "username": apiRes.Result.Username,
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-// https://core.telegram.org/bots/api#making-requests
-type apiResponse struct {
- Ok bool `json:"ok"`
- Result *userResponse `json:"result"`
-}
-
-// https://core.telegram.org/bots/api#user
-type userResponse struct {
- IsBot bool `json:"is_bot"`
- Username string `json:"username"`
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TelegramBotToken
-}
-
-func (s Scanner) Description() string {
- return "Telegram Bot API tokens are used to authenticate requests to the Telegram Bot API. They can be used to control and interact with Telegram bots."
-}
diff --git a/pkg/detectors/telegrambottoken/telegrambottoken_integration_test.go b/pkg/detectors/telegrambottoken/telegrambottoken_integration_test.go
deleted file mode 100644
index bd7aab393043..000000000000
--- a/pkg/detectors/telegrambottoken/telegrambottoken_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package telegrambottoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTelegramBotToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TELEGRAMBOTTOKEN_TOKEN")
- inactiveSecret := testSecrets.MustGetField("TELEGRAMBOTTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a telegrambottoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TelegramBotToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a telegrambottoken secret %s within", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TelegramBotToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TelegramBotToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TelegramBotToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/telegrambottoken/telegrambottoken_test.go b/pkg/detectors/telegrambottoken/telegrambottoken_test.go
deleted file mode 100644
index d500a0d5ed61..000000000000
--- a/pkg/detectors/telegrambottoken/telegrambottoken_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package telegrambottoken
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "869511423:BctOM4-oBANUkq653PAPbo4HLs3DCd9NCDe"
- invalidPattern = "869511423:BctOM4-oBANU?q653PAPbo4HLs3DCd9NCDe"
- keyword = "telegrambottoken"
-)
-
-func TestTelegramBotToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword telegrambottoken",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/teletype/teletype.go b/pkg/detectors/teletype/teletype.go
deleted file mode 100644
index a939fe8a6dff..000000000000
--- a/pkg/detectors/teletype/teletype.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package teletype
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"teletype"}) + `\b([0-9a-zA-Z-]{64})\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{"teletype"}
-}
-
-// FromData will find and optionally verify Teletype secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Teletype,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.teletype.app/public/api/v1/messages", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-Auth-Token", resMatch)
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
-
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"code":401`)
-
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = false
- } else {
- s1.Verified = true
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Teletype
-}
-
-func (s Scanner) Description() string {
- return "Teletype is a messaging service. Teletype API keys can be used to access and send messages through the Teletype API."
-}
diff --git a/pkg/detectors/teletype/teletype_integration_test.go b/pkg/detectors/teletype/teletype_integration_test.go
deleted file mode 100644
index c36edd0e604e..000000000000
--- a/pkg/detectors/teletype/teletype_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package teletype
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTeletype_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TELETYPE")
- inactiveSecret := testSecrets.MustGetField("TELETYPE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a teletype secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Teletype,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a teletype secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Teletype,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Teletype.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Teletype.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/teletype/teletype_test.go b/pkg/detectors/teletype/teletype_test.go
deleted file mode 100644
index bc0f26d26e68..000000000000
--- a/pkg/detectors/teletype/teletype_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package teletype
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "WdEUY8VfTBjpSpHlrhjbsdarYSVf-jXZfwYPCt1ckQLBlJ3GX-p9xs3dBor0ekHT"
- invalidPattern = "WdEUY8V?TBjpSpHlrhjbsdarYSVf-jXZfwYPCt1ckQLBlJ3GX-p9xs3dBor0ekHT"
- keyword = "teletype"
-)
-
-func TestTeletype_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword teletype",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/telnyx/telnyx.go b/pkg/detectors/telnyx/telnyx.go
deleted file mode 100644
index 6d56ee1ee0a6..000000000000
--- a/pkg/detectors/telnyx/telnyx.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package telnyx
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"telnyx"}) + `\b(KEY[0-9A-Za-z_-]{55})\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{"telnyx"}
-}
-
-// FromData will find and optionally verify Telnyx secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Telnyx,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.telnyx.com/v2/messaging_profiles", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.telnyx+json; version=3")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Telnyx
-}
-
-func (s Scanner) Description() string {
- return "Telnyx is a communications platform offering voice, messaging, and other communication services. Telnyx keys can be used to access and manage these services."
-}
diff --git a/pkg/detectors/telnyx/telnyx_integration_test.go b/pkg/detectors/telnyx/telnyx_integration_test.go
deleted file mode 100644
index 7eb3a8848482..000000000000
--- a/pkg/detectors/telnyx/telnyx_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package telnyx
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTelnyx_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TELNYX")
- inactiveSecret := testSecrets.MustGetField("TELNYX_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a telnyx secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Telnyx,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a telnyx secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Telnyx,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Telnyx.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Telnyx.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/telnyx/telnyx_test.go b/pkg/detectors/telnyx/telnyx_test.go
deleted file mode 100644
index 688d90243c83..000000000000
--- a/pkg/detectors/telnyx/telnyx_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package telnyx
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "KEYAptrcGNPkrkfIifpxSr1PAf5ChNcx-APIMKz9_ImMMXli1w16QD97i5"
- invalidPattern = "KEYAptrcG?PkrkfIifpxSr1PAf5ChNcx-APIMKz9_ImMMXli1w16QD97i5"
- keyword = "telnyx"
-)
-
-func TestTelnyx_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword telnyx",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken.go b/pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken.go
deleted file mode 100644
index fe71f1999eda..000000000000
--- a/pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package terraformcloudpersonaltoken
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(`\b([A-Za-z0-9]{14}.atlasv1.[A-Za-z0-9]{67})\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{".atlasv1."}
-}
-
-// FromData will find and optionally verify TerraformCloudPersonalToken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TerraformCloudPersonalToken,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://app.terraform.io/api/v2/account/details", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TerraformCloudPersonalToken
-}
-
-func (s Scanner) Description() string {
- return "Terraform Cloud is a service that provides infrastructure automation. Terraform Cloud personal tokens can be used to access and manage infrastructure as code."
-}
diff --git a/pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken_integration_test.go b/pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken_integration_test.go
deleted file mode 100644
index 2d0f54d3bb6d..000000000000
--- a/pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken_integration_test.go
+++ /dev/null
@@ -1,143 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package terraformcloudpersonaltoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTerraformCloudPersonalToken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TERRAFORMCLOUDPERSONALTOKEN_TOKEN")
- inactiveSecret := testSecrets.MustGetField("TERRAFORMCLOUDPERSONALTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a terra secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TerraformCloudPersonalToken,
- Verified: true,
- },
- },
- wantErr: false,
- },
-
- {
- name: "found, unverified in json",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf(`{
- "credentials": {
- "app.terraform.io": {
- "token": "%s"
- }
- }
- }`, inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TerraformCloudPersonalToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a terra secret %s within but unverified", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TerraformCloudPersonalToken,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TerraformCloudPersonalToken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TerraformCloudPersonalToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken_test.go b/pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken_test.go
deleted file mode 100644
index 47d556c1bd60..000000000000
--- a/pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package terraformcloudpersonaltoken
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "HMu97kXVPMUVOv.atlasv1.RjI0iSluLRxMTVU9x04xUV5iwpgGkHXnLtyVgNg2v7iedttRzvjMNuvisQob0OVaDLH"
- invalidPattern = "HMu97kXVPMUVOv.atlasv1.RjI0iSluLRxMTVU9x04xUV?iwpgGkHXnLtyVgNg2v7iedttRzvjMNuvisQob0OVaDLH"
- keyword = "terraformcloudpersonaltoken"
-)
-
-func TestTerraformCloudPersonalToken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword terraformcloudpersonaltoken",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/testingbot/testingbot.go b/pkg/detectors/testingbot/testingbot.go
deleted file mode 100644
index e145345d36e8..000000000000
--- a/pkg/detectors/testingbot/testingbot.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package testingbot
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"testingbot"}) + `\b([0-9a-z]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"testingbot"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"testingbot"}
-}
-
-// FromData will find and optionally verify TestingBot secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueIDMatches, uniqueKeyMatches := make(map[string]struct{}), make(map[string]struct{})
-
- for _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueIDMatches[match[1]] = struct{}{}
- }
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeyMatches[match[1]] = struct{}{}
- }
-
- for id := range uniqueIDMatches {
- for key := range uniqueKeyMatches {
- if id == key {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TestingBot,
- Raw: []byte(key),
- }
-
- if verify {
- isVerified, verificationErr := verifyTestingBot(ctx, client, id, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, key)
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TestingBot
-}
-
-func (s Scanner) Description() string {
- return "TestingBot provides cross-browser testing services. TestingBot credentials can be used to automate tests on various browsers and devices."
-}
-
-func verifyTestingBot(ctx context.Context, client *http.Client, id, secret string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.testingbot.com/v1/user", nil)
- if err != nil {
- return false, err
- }
-
- req.SetBasicAuth(id, secret)
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/testingbot/testingbot_integration_test.go b/pkg/detectors/testingbot/testingbot_integration_test.go
deleted file mode 100644
index 959a8e411e98..000000000000
--- a/pkg/detectors/testingbot/testingbot_integration_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package testingbot
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTestingBot_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TESTINGBOT")
- id := testSecrets.MustGetField("TESTINGBOT_USER")
- inactiveSecret := testSecrets.MustGetField("TESTINGBOT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a testingbot secret %s within testingbot %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TestingBot,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_TestingBot,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a testingbot secret %s within testingbot %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TestingBot,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_TestingBot,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TestingBot.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TestingBot.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/testingbot/testingbot_test.go b/pkg/detectors/testingbot/testingbot_test.go
deleted file mode 100644
index 8851804ac3e4..000000000000
--- a/pkg/detectors/testingbot/testingbot_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package testingbot
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "tyg635h477e5zf1cbvk94xjwam20mjn1"
- invalidKey = "tyg635h477e5zf1c?vk94xjwam20mjn1"
- validId = "apelbd5muwijaybdgn920qn64xjgnsf1"
- invalidId = "apelbd5muwijaybd?n920qn64xjgnsf1"
- keyword = "testingbot"
-)
-
-func TestTestingBot_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword testingbot",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validId, validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/textmagic/textmagic.go b/pkg/detectors/textmagic/textmagic.go
deleted file mode 100644
index d1a4ef054380..000000000000
--- a/pkg/detectors/textmagic/textmagic.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package textmagic
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"textmagic"}) + `\b([0-9A-Za-z]{30})\b`)
- userPat = regexp.MustCompile(detectors.PrefixRegex([]string{"textmagic"}) + `\b([0-9A-Za-z]{1,25})\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{"textmagic"}
-}
-
-// FromData will find and optionally verify Textmagic secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- userMatches := userPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, userMatch := range userMatches {
- resUser := strings.TrimSpace(userMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Textmagic,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resUser),
- }
-
- if verify {
- data := fmt.Sprintf("%s:%s", resUser, resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://rest.textmagic.com/api/v2/user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Textmagic
-}
-
-func (s Scanner) Description() string {
- return "Textmagic is a service for sending and receiving text messages. Textmagic API keys can be used to access and manage text messaging services."
-}
diff --git a/pkg/detectors/textmagic/textmagic_integration_test.go b/pkg/detectors/textmagic/textmagic_integration_test.go
deleted file mode 100644
index 251e20b12f57..000000000000
--- a/pkg/detectors/textmagic/textmagic_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package textmagic
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTextmagic_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TEXTMAGIC")
- user := testSecrets.MustGetField("SCANNER_USERNAME")
- inactiveSecret := testSecrets.MustGetField("TEXTMAGIC_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a textmagic secret %s within textmagic user %s", secret, user)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Textmagic,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a textmagic secret %s within textmagic user %s but not valid", inactiveSecret, user)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Textmagic,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Textmagic.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Textmagic.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/textmagic/textmagic_test.go b/pkg/detectors/textmagic/textmagic_test.go
deleted file mode 100644
index 0fe73c100f05..000000000000
--- a/pkg/detectors/textmagic/textmagic_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package textmagic
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "hnNIvpj0lNNChF5z5sAzsgku4CuL0u"
- invalidKey = "hnNIvpj0lNNChF5?5sAzsgku4CuL0u"
- validUser = "kKVO9ZYWvpcZS0Zozc"
- invalidUser = "?KVO9ZYWvp?ZS0Zozc"
- keyword = "textmagic"
-)
-
-func TestTextmagic_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword textmagic",
- input: fmt.Sprintf("%s '%s'\n'%s'\n", keyword, validKey, validUser),
- want: []string{validKey + validUser},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s '%s'\n'%s'\n", keyword, invalidKey, invalidUser),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/theoddsapi/theoddsapi.go b/pkg/detectors/theoddsapi/theoddsapi.go
deleted file mode 100644
index 4be4baf30fbe..000000000000
--- a/pkg/detectors/theoddsapi/theoddsapi.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package theoddsapi
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"theoddsapi", "the-odds-api"}) + `\b([0-9a-f]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"theoddsapi", "the-odds-api"}
-}
-
-// FromData will find and optionally verify TheOddsApi secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TheOddsApi,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.the-odds-api.com/v4/sports/?apiKey="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TheOddsApi
-}
-
-func (s Scanner) Description() string {
- return "TheOddsApi provides sports data and odds. TheOddsApi keys can be used to access this data."
-}
diff --git a/pkg/detectors/theoddsapi/theoddsapi_integration_test.go b/pkg/detectors/theoddsapi/theoddsapi_integration_test.go
deleted file mode 100644
index d4a5aead7daf..000000000000
--- a/pkg/detectors/theoddsapi/theoddsapi_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package theoddsapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTheOddsApi_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("THEODDSAPI")
- inactiveSecret := testSecrets.MustGetField("THEODDSAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a theoddsapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TheOddsApi,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a theoddsapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TheOddsApi,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TheOddsApi.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TheOddsApi.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/theoddsapi/theoddsapi_test.go b/pkg/detectors/theoddsapi/theoddsapi_test.go
deleted file mode 100644
index 28257655371b..000000000000
--- a/pkg/detectors/theoddsapi/theoddsapi_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package theoddsapi
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "237eb668c01429518426aa82b374c9da"
- invalidPattern = "237eb66?c01429518426aa82b374c9da"
- keyword = "theoddsapi"
-)
-
-func TestTheOddsApi_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword theoddsapi",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/thinkific/thinkific.go b/pkg/detectors/thinkific/thinkific.go
deleted file mode 100644
index 91ddaea61118..000000000000
--- a/pkg/detectors/thinkific/thinkific.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package thinkific
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"thinkific"}) + `\b([0-9a-f]{32})\b`)
- domainPat = regexp.MustCompile(detectors.PrefixRegex([]string{"thinkific"}) + `\b([0-9A-Za-z]{4,40})\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{"thinkific"}
-}
-
-// FromData will find and optionally verify Thinkific secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- domainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, domainMatch := range domainMatches {
- resDomainMatch := strings.TrimSpace(domainMatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Thinkific,
- Raw: []byte(resMatch),
- }
-
- if verify {
- domainRes := fmt.Sprintf("%s-s-school", resDomainMatch)
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.thinkific.com/api/public/v1/collections", nil)
- if err != nil {
- continue
- }
- req.Header.Add("X-Auth-API-Key", resMatch)
- req.Header.Add("X-Auth-Subdomain", domainRes)
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
-
- if strings.Contains(body, "API Access is not available") {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Thinkific
-}
-
-func (s Scanner) Description() string {
- return "Thinkific is an online course platform that allows users to create, market, and sell online courses. Thinkific API keys can be used to access and manage course data and user information."
-}
diff --git a/pkg/detectors/thinkific/thinkific_integration_test.go b/pkg/detectors/thinkific/thinkific_integration_test.go
deleted file mode 100644
index d9445616c73f..000000000000
--- a/pkg/detectors/thinkific/thinkific_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package thinkific
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestThinkific_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("THINKIFIC")
- inactiveSecret := testSecrets.MustGetField("THINKIFIC_INACTIVE")
- domain := testSecrets.MustGetField("THINKIFIC_DOMAIN")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a thinkific secret %s within thinkificdom %s", secret, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Thinkific,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a thinkific secret %s within thinkificdom %s but not valid", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Thinkific,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Thinkific.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Thinkific.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/thinkific/thinkific_test.go b/pkg/detectors/thinkific/thinkific_test.go
deleted file mode 100644
index c5105c7d09e9..000000000000
--- a/pkg/detectors/thinkific/thinkific_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package thinkific
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "35992abe3b6b861c66d4e1d668ac9367"
- invalidKey = "35992abe3b?b861c66d4e1d668ac9367"
- validDomain = "orzBcpvjg21Oj2NPayZ3cZOOfaJpS7GwXLt6pP"
- invalidDomain = "?rzBcpvjg21Oj2N?ayZ3cZOOfaJpS7GwXLt6pP"
- keyword = "thinkific"
-)
-
-func TestThinkific_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword thinkific",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validDomain),
- want: []string{validKey, validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidDomain),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/thousandeyes/thousandeyes.go b/pkg/detectors/thousandeyes/thousandeyes.go
deleted file mode 100644
index 735905b45e95..000000000000
--- a/pkg/detectors/thousandeyes/thousandeyes.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package thousandeyes
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"thousandeyes"}) + `\b([a-zA-Z0-9]{32})\b`)
- email = regexp.MustCompile(detectors.PrefixRegex([]string{"thousandeyes"}) + `\b([a-zA-Z0-9]{3,20}@[a-zA-Z0-9]{2,12}.[a-zA-Z0-9]{2,5})\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{"thousandeyes"}
-}
-
-// FromData will find and optionally verify ThousandEyes secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- emailMatches := email.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- tokenPatMatch := strings.TrimSpace(match[1])
-
- for _, emailMatch := range emailMatches {
-
- userPatMatch := strings.TrimSpace(emailMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ThousandEyes,
- Raw: []byte(tokenPatMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.thousandeyes.com/v6/endpoint-data/user-sessions/web.json", nil)
- if err != nil {
- continue
- }
- req.SetBasicAuth(userPatMatch, tokenPatMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ThousandEyes
-}
-
-func (s Scanner) Description() string {
- return "ThousandEyes provides network intelligence and monitoring services. ThousandEyes API keys can be used to access and manage network performance data."
-}
diff --git a/pkg/detectors/thousandeyes/thousandeyes_integration_test.go b/pkg/detectors/thousandeyes/thousandeyes_integration_test.go
deleted file mode 100644
index 8a5ea0bead86..000000000000
--- a/pkg/detectors/thousandeyes/thousandeyes_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package thousandeyes
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestThousandEyes_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("THOUSANDEYES")
- email := testSecrets.MustGetField("THOUSANDEYES_USER")
- inactiveSecret := testSecrets.MustGetField("THOUSANDEYES_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a thousandeyes secret %s within thousandeyes %s", secret, email)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ThousandEyes,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a thousandeyes secret %s within thousandeyes %s but not valid", inactiveSecret, email)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ThousandEyes,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ThousandEyes.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ThousandEyes.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/thousandeyes/thousandeyes_test.go b/pkg/detectors/thousandeyes/thousandeyes_test.go
deleted file mode 100644
index 1a6ec8e087c6..000000000000
--- a/pkg/detectors/thousandeyes/thousandeyes_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package thousandeyes
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "QRv5GRXQjWmQ6esvXuvk3pcw0IMAw5NO"
- invalidKey = "QRv5GR?QjWmQ6esvXuvk3pcw0IMAw5NO"
- validEmail = "ejtaK2kkwuVx@1U6Evvih9YeT"
- invalidEmail = "ejtaK2kkwuVx?1U6Evvih9YeT"
- keyword = "thousandeyes"
-)
-
-func TestThousandEyes_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword thousandeyes",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validEmail),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidEmail),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/ticketmaster/ticketmaster.go b/pkg/detectors/ticketmaster/ticketmaster.go
deleted file mode 100644
index 1384ca5c1799..000000000000
--- a/pkg/detectors/ticketmaster/ticketmaster.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package ticketmaster
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ticketmaster"}) + `\b([a-zA-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"ticketmaster"}
-}
-
-// FromData will find and optionally verify TicketMaster secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TicketMaster,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://app.ticketmaster.com/discovery/v2/events.json?apikey=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TicketMaster
-}
-
-func (s Scanner) Description() string {
- return "TicketMaster API keys can be used to access and modify event data and other related services provided by TicketMaster."
-}
diff --git a/pkg/detectors/ticketmaster/ticketmaster_integration_test.go b/pkg/detectors/ticketmaster/ticketmaster_integration_test.go
deleted file mode 100644
index 6cdbefb91d97..000000000000
--- a/pkg/detectors/ticketmaster/ticketmaster_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package ticketmaster
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTicketMaster_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TICKETMASTER")
- inactiveSecret := testSecrets.MustGetField("TICKETMASTER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ticketmaster secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TicketMaster,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ticketmaster secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TicketMaster,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TicketMaster.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TicketMaster.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/ticketmaster/ticketmaster_test.go b/pkg/detectors/ticketmaster/ticketmaster_test.go
deleted file mode 100644
index 0458e155a078..000000000000
--- a/pkg/detectors/ticketmaster/ticketmaster_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package ticketmaster
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "QSLwTBLaOcyPYh9KW1edgeiyPxqLT1KK"
- invalidPattern = "QSLwTBLaOcyPYh9K?1edgeiyPxqLT1KK"
- keyword = "ticketmaster"
-)
-
-func TestTicketMaster_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword ticketmaster",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tickettailor/tickettailor.go b/pkg/detectors/tickettailor/tickettailor.go
deleted file mode 100644
index 587cfe32e8c6..000000000000
--- a/pkg/detectors/tickettailor/tickettailor.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package tickettailor
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tickettailor"}) + `\b(sk_[0-9]{4}_[0-9]{6}_[a-f0-9]{32})`)
-)
-
-// 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{"tickettailor"}
-}
-
-// FromData will find and optionally verify Tickettailor secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueKeyMatches := make(map[string]struct{})
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeyMatches[match[1]] = struct{}{}
- }
-
- for key := range uniqueKeyMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Tickettailor,
- Raw: []byte(key),
- }
-
- if verify {
- isVerified, verificationErr := verifyTicketTailor(ctx, client, key)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Tickettailor
-}
-
-func (s Scanner) Description() string {
- return "Tickettailor is an online ticketing platform that allows event organizers to sell tickets. Tickettailor API keys can be used to manage events, orders, and tickets programmatically."
-}
-
-func verifyTicketTailor(ctx context.Context, client *http.Client, apiKey string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.tickettailor.com/v1/orders", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Accept", "application/json")
- // as per API docs we only need to use apiKey as username in basic auth and leave password as empty: https://developers.tickettailor.com/#authentication
- req.SetBasicAuth(apiKey, "")
- resp, err := client.Do(req)
- if err != nil {
- return false, nil
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/tickettailor/tickettailor_integration_test.go b/pkg/detectors/tickettailor/tickettailor_integration_test.go
deleted file mode 100644
index c391c47d6d0e..000000000000
--- a/pkg/detectors/tickettailor/tickettailor_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tickettailor
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTickettailor_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TICKETTAILOR")
- inactiveSecret := testSecrets.MustGetField("TICKETTAILOR_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tickettailor secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tickettailor,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tickettailor secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tickettailor,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tickettailor.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Tickettailor.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tickettailor/tickettailor_test.go b/pkg/detectors/tickettailor/tickettailor_test.go
deleted file mode 100644
index 88913652daa9..000000000000
--- a/pkg/detectors/tickettailor/tickettailor_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tickettailor
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "sk_6551_225099_d9a4d4b7d506fba4d2cbb2ed803d088b"
- invalidPattern = "sk_1234_225099_WW6E3TND0PXT5L?LPeOfVG7c2Y92fWNR"
- keyword = "tickettailor"
-)
-
-func TestTickettailor_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tickettailor",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tiingo/tiingo.go b/pkg/detectors/tiingo/tiingo.go
deleted file mode 100644
index 3b1c00049730..000000000000
--- a/pkg/detectors/tiingo/tiingo.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package tiingo
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tiingo"}) + `\b([0-9a-z]{40})\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{"tiingo"}
-}
-
-// FromData will find and optionally verify Tiingo secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Tiingo,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.tiingo.com/tiingo/fundamentals/definitions", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Tiingo
-}
-
-func (s Scanner) Description() string {
- return "Tiingo is a financial data platform that provides access to various financial data and APIs. Tiingo API keys can be used to access and retrieve financial data."
-}
diff --git a/pkg/detectors/tiingo/tiingo_integration_test.go b/pkg/detectors/tiingo/tiingo_integration_test.go
deleted file mode 100644
index 64e75d3dca19..000000000000
--- a/pkg/detectors/tiingo/tiingo_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tiingo
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTiingo_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TIINGO")
- inactiveSecret := testSecrets.MustGetField("TIINGO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tiingo secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tiingo,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tiingo secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tiingo,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tiingo.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Tiingo.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tiingo/tiingo_test.go b/pkg/detectors/tiingo/tiingo_test.go
deleted file mode 100644
index 51dd57559365..000000000000
--- a/pkg/detectors/tiingo/tiingo_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tiingo
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "r2ofhhzv4tb7mv2c7o32fd9vlusg03kc76dygnk2"
- invalidPattern = "r2o?hhzv4tb7mv2c7o32fd9vlusg03kc76dygnk2"
- keyword = "tiingo"
-)
-
-func TestTiingo_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tiingo",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/timecamp/timecamp.go b/pkg/detectors/timecamp/timecamp.go
deleted file mode 100644
index e5942668f9cc..000000000000
--- a/pkg/detectors/timecamp/timecamp.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package timecamp
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"timecamp"}) + `\b([0-9a-z]{26})\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{"timecamp"}
-}
-
-// FromData will find and optionally verify TimeCamp secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TimeCamp,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://app.timecamp.com/third_party/api/user?format=json", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.timecamp+json; version=3")
- req.Header.Add("Authorization", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TimeCamp
-}
-
-func (s Scanner) Description() string {
- return "TimeCamp is a time tracking software for teams and freelancers. TimeCamp API keys can be used to access and modify time tracking data."
-}
diff --git a/pkg/detectors/timecamp/timecamp_integration_test.go b/pkg/detectors/timecamp/timecamp_integration_test.go
deleted file mode 100644
index d091ae3ec4c8..000000000000
--- a/pkg/detectors/timecamp/timecamp_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package timecamp
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTimeCamp_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TIMECAMP")
- inactiveSecret := testSecrets.MustGetField("TIMECAMP_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a timecamp secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TimeCamp,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a timecamp secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TimeCamp,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TimeCamp.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TimeCamp.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/timecamp/timecamp_test.go b/pkg/detectors/timecamp/timecamp_test.go
deleted file mode 100644
index 563037265874..000000000000
--- a/pkg/detectors/timecamp/timecamp_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package timecamp
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "66mtci6qdo8ccczw48j5lpt2uw"
- invalidPattern = "66mt?i6qdo8ccczw48j5lpt2uw"
- keyword = "timecamp"
-)
-
-func TestTimeCamp_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword timecamp",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/timezoneapi/timezoneapi.go b/pkg/detectors/timezoneapi/timezoneapi.go
deleted file mode 100644
index 617136016720..000000000000
--- a/pkg/detectors/timezoneapi/timezoneapi.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package timezoneapi
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"timezoneapi"}) + `\b([a-zA-Z]{20})\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{"timezoneapi"}
-}
-
-// FromData will find and optionally verify Timezoneapi secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Timezoneapi,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://timezoneapi.io/api/ip/?token="+resMatch, nil)
- if err != nil {
- continue
- }
-
- res, err := client.Do(req)
- if err == nil {
- verifiedBodyResponse, err := common.ResponseContainsSubstring(res.Body, "date")
- if err != nil {
- return nil, err
- }
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 && verifiedBodyResponse {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Timezoneapi
-}
-
-func (s Scanner) Description() string {
- return "Timezoneapi is a service that provides time zone information. Timezoneapi keys can be used to access and retrieve time zone data."
-}
diff --git a/pkg/detectors/timezoneapi/timezoneapi_integration_test.go b/pkg/detectors/timezoneapi/timezoneapi_integration_test.go
deleted file mode 100644
index 8a3accdd80b0..000000000000
--- a/pkg/detectors/timezoneapi/timezoneapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package timezoneapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTimezoneapi_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TIMEZONEAPI")
- inactiveSecret := testSecrets.MustGetField("TIMEZONEAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a timezoneapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Timezoneapi,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a timezoneapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Timezoneapi,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Timezoneapi.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Timezoneapi.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/timezoneapi/timezoneapi_test.go b/pkg/detectors/timezoneapi/timezoneapi_test.go
deleted file mode 100644
index 09993a459952..000000000000
--- a/pkg/detectors/timezoneapi/timezoneapi_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package timezoneapi
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "SSVmFDNCFfOHKXQAEckx"
- invalidPattern = "SSVmFDNCFf?HKXQAEckx"
- keyword = "timezoneapi"
-)
-
-func TestTimezoneapi_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword timezoneapi",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tineswebhook/tineswebhook.go b/pkg/detectors/tineswebhook/tineswebhook.go
deleted file mode 100644
index 34fc3a989960..000000000000
--- a/pkg/detectors/tineswebhook/tineswebhook.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package tineswebhook
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`(https://[\w-]+\.tines\.com/webhook/[a-z0-9]{32}/[a-z0-9]{32})`)
-)
-
-// 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{"tines.com"}
-}
-
-// FromData will find and optionally verify TinesWebhook secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TinesWebhook,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(``)
- req, err := http.NewRequestWithContext(ctx, "GET", resMatch, payload)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TinesWebhook
-}
-
-func (s Scanner) Description() string {
- return "Tines is an automation platform. Tines Webhook URLs can be used to trigger and interact with Tines workflows."
-}
diff --git a/pkg/detectors/tineswebhook/tineswebhook_integration_test.go b/pkg/detectors/tineswebhook/tineswebhook_integration_test.go
deleted file mode 100644
index f80b9e7fb626..000000000000
--- a/pkg/detectors/tineswebhook/tineswebhook_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tineswebhook
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTinesWebhook_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TINESWEBHOOK_TOKEN")
- inactiveSecret := testSecrets.MustGetField("TINESWEBHOOK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tineswebhook secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TinesWebhook,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tineswebhook secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TinesWebhook,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TinesWebhook.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TinesWebhook.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tineswebhook/tineswebhook_test.go b/pkg/detectors/tineswebhook/tineswebhook_test.go
deleted file mode 100644
index cc858d78ffa9..000000000000
--- a/pkg/detectors/tineswebhook/tineswebhook_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package tineswebhook
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "https://efT.tines.com/webhook/4j55coiuyiladq9ydaoo4oan89u56vkm/u37txygi11dou8u2ttwe4rl63ax4lnw9"
- invalidPattern = "https://efT.tines.com/webhook/4j55coiuyil?dq9ydaoo4oan89u56vkm/u37txygi11dou8u2ttwe4rl63ax4lnw9"
- keyword = "tineswebhook"
-)
-
-func TestTinesWebhook_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tineswebhook",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tly/tly.go b/pkg/detectors/tly/tly.go
deleted file mode 100644
index 0a31e210c84b..000000000000
--- a/pkg/detectors/tly/tly.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package tly
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tly"}) + `\b([0-9A-Za-z]{60})\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{"tly"}
-}
-
-// FromData will find and optionally verify TLy secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TLy,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://t.ly/api/v1/link/stats?api_token="+resMatch+"&short_url=https://t.ly/h9YS", nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TLy
-}
-
-func (s Scanner) Description() string {
- return "TLy is a URL shortening service. TLy API keys can be used to access and manage shortened URLs."
-}
diff --git a/pkg/detectors/tly/tly_integration_test.go b/pkg/detectors/tly/tly_integration_test.go
deleted file mode 100644
index c151f420dcac..000000000000
--- a/pkg/detectors/tly/tly_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tly
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTLy_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TLY")
- inactiveSecret := testSecrets.MustGetField("TLY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tly secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TLy,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tly secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TLy,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TLy.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TLy.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tly/tly_test.go b/pkg/detectors/tly/tly_test.go
deleted file mode 100644
index 8723aad29ba6..000000000000
--- a/pkg/detectors/tly/tly_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tly
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "VLmhHAQq7kjrhxrt7x07pJjnafVRara2aoqPndSOepXH2MOY3CcloWwG6ZcD"
- invalidPattern = "VLmhHAQq7kjrhxrt7x07pJjn?fVRara2aoqPndSOepXH2MOY3CcloWwG6ZcD"
- keyword = "tly"
-)
-
-func TestTLy_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tly",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tmetric/tmetric.go b/pkg/detectors/tmetric/tmetric.go
deleted file mode 100644
index 636d91921389..000000000000
--- a/pkg/detectors/tmetric/tmetric.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package tmetric
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tmetric"}) + `\b([0-9A-Z]{64})\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{"tmetric"}
-}
-
-// FromData will find and optionally verify Tmetric secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Tmetric,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://app.tmetric.com/api/v3/user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Tmetric
-}
-
-func (s Scanner) Description() string {
- return "Tmetric is a time tracking service. Tmetric API keys can be used to access and manage time tracking data."
-}
diff --git a/pkg/detectors/tmetric/tmetric_integration_test.go b/pkg/detectors/tmetric/tmetric_integration_test.go
deleted file mode 100644
index dfe3154dd306..000000000000
--- a/pkg/detectors/tmetric/tmetric_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tmetric
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTmetric_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TMETRIC")
- inactiveSecret := testSecrets.MustGetField("TMETRIC_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tmetric secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tmetric,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tmetric secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tmetric,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tmetric.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Tmetric.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tmetric/tmetric_test.go b/pkg/detectors/tmetric/tmetric_test.go
deleted file mode 100644
index becf3cbce4e0..000000000000
--- a/pkg/detectors/tmetric/tmetric_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tmetric
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "379NU836GQGRFS7Q6MBPKY5GX8F16AF2EKSUB3X4KA2RWKZPU3I3S7R7QVJPPMG5"
- invalidPattern = "379NU836GQGRFS7Q6MBPKY5GX8F16AF2?KSUB3X4KA2RWKZPU3I3S7R7QVJPPMG5"
- keyword = "tmetric"
-)
-
-func TestTmetric_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tmetric",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/todoist/todoist.go b/pkg/detectors/todoist/todoist.go
deleted file mode 100644
index 06d11228cbe6..000000000000
--- a/pkg/detectors/todoist/todoist.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package todoist
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"todoist"}) + `\b([0-9a-z]{40})\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{"todoist"}
-}
-
-// FromData will find and optionally verify Todoist secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Todoist,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.todoist.com/rest/v2/projects", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Todoist
-}
-
-func (s Scanner) Description() string {
- return "Todoist is a task management application. Todoist API keys can be used to access and manage tasks and projects within a user's account."
-}
diff --git a/pkg/detectors/todoist/todoist_integration_test.go b/pkg/detectors/todoist/todoist_integration_test.go
deleted file mode 100644
index de8f825b06bc..000000000000
--- a/pkg/detectors/todoist/todoist_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package todoist
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTodoist_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TODOIST")
- inactiveSecret := testSecrets.MustGetField("TODOIST_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a todoist secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Todoist,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a todoist secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Todoist,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Todoist.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Todoist.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/todoist/todoist_test.go b/pkg/detectors/todoist/todoist_test.go
deleted file mode 100644
index 907b32203775..000000000000
--- a/pkg/detectors/todoist/todoist_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package todoist
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "qpgv7z8amkp4ln55znaacezm9jy35wcayy6bya2r"
- invalidPattern = "qpgv7z8amkp4ln55znaa?ezm9jy35wcayy6bya2r"
- keyword = "todoist"
-)
-
-func TestTodoist_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword todoist",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/toggltrack/toggltrack.go b/pkg/detectors/toggltrack/toggltrack.go
deleted file mode 100644
index 69960abf6f56..000000000000
--- a/pkg/detectors/toggltrack/toggltrack.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package toggltrack
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"toggl"}) + `\b([0-9Aa-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"toggl"}
-}
-
-// FromData will find and optionally verify TogglTrack secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TogglTrack,
- Raw: []byte(resMatch),
- }
-
- if verify {
- data := fmt.Sprintf("%s:api_token", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
-
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.track.toggl.com/api/v8/me", nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TogglTrack
-}
-
-func (s Scanner) Description() string {
- return "TogglTrack is a time tracking tool. TogglTrack API keys can be used to access and manage time tracking data."
-}
diff --git a/pkg/detectors/toggltrack/toggltrack_integration_test.go b/pkg/detectors/toggltrack/toggltrack_integration_test.go
deleted file mode 100644
index 2e21c7ef7e17..000000000000
--- a/pkg/detectors/toggltrack/toggltrack_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package toggltrack
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTogglTrack_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TOGGLTRACK")
- inactiveSecret := testSecrets.MustGetField("TOGGLTRACK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a toggltrack secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TogglTrack,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a toggltrack secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TogglTrack,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TogglTrack.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TogglTrack.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/toggltrack/toggltrack_test.go b/pkg/detectors/toggltrack/toggltrack_test.go
deleted file mode 100644
index 9725da4a71b8..000000000000
--- a/pkg/detectors/toggltrack/toggltrack_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package toggltrack
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "5hj12f7pnz8ha0rqxewpqvsuc8hzgy9h"
- invalidPattern = "5hj12f7pnz8ha0rq?ewpqvsuc8hzgy9h"
- keyword = "toggltrack"
-)
-
-func TestTogglTrack_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword toggltrack",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tokeet/tokeet.go b/pkg/detectors/tokeet/tokeet.go
deleted file mode 100644
index e580299dc63e..000000000000
--- a/pkg/detectors/tokeet/tokeet.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package tokeet
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tokeet"}) + `\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tokeet"}) + `\b([0-9]{10}.[0-9]{4})\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{"tokeet"}
-}
-
-// FromData will find and optionally verify Tokeet secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Tokeet,
- Raw: []byte(resMatch),
- }
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://capi.tokeet.com/v1/user?account=%s", resIdMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Tokeet
-}
-
-func (s Scanner) Description() string {
- return "Tokeet is a property management software used for managing rental properties. Tokeet API keys can be used to access and modify property data and manage bookings."
-}
diff --git a/pkg/detectors/tokeet/tokeet_integration_test.go b/pkg/detectors/tokeet/tokeet_integration_test.go
deleted file mode 100644
index a0f5254c0739..000000000000
--- a/pkg/detectors/tokeet/tokeet_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tokeet
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTokeet_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TOKEET")
- inactiveSecret := testSecrets.MustGetField("TOKEET_INACTIVE")
- id := testSecrets.MustGetField("TOKEET_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tokeet secret %s within tokeet %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tokeet,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tokeet secret %s within but not valid tokeet %s", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tokeet,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tokeet.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Tokeet.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tokeet/tokeet_test.go b/pkg/detectors/tokeet/tokeet_test.go
deleted file mode 100644
index bcf05a734272..000000000000
--- a/pkg/detectors/tokeet/tokeet_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package tokeet
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "d57fe072-befa-4df5-42f1-014f9d253699"
- invalidKey = "d57fe072?befa-4df5-42f1-014f9d253699"
- validId = "5573995420@7486"
- invalidId = "5573995?20@7486"
- keyword = "tokeet"
-)
-
-func TestTokeet_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tokeet",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tomorrowio/tomorrowio.go b/pkg/detectors/tomorrowio/tomorrowio.go
deleted file mode 100644
index 5bc673fe1d61..000000000000
--- a/pkg/detectors/tomorrowio/tomorrowio.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package tomorrowio
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tomorrow"}) + `\b([a-zA-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"tomorrow"}
-}
-
-// FromData will find and optionally verify TomorrowIO secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TomorrowIO,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.tomorrow.io/v4/alerts?apikey=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TomorrowIO
-}
-
-func (s Scanner) Description() string {
- return "TomorrowIO is a weather intelligence platform providing weather data and insights. TomorrowIO API keys can be used to access and retrieve weather information."
-}
diff --git a/pkg/detectors/tomorrowio/tomorrowio_integration_test.go b/pkg/detectors/tomorrowio/tomorrowio_integration_test.go
deleted file mode 100644
index 4e76e8475daf..000000000000
--- a/pkg/detectors/tomorrowio/tomorrowio_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tomorrowio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTomorrowIO_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TOMORROWIO")
- inactiveSecret := testSecrets.MustGetField("TOMORROWIO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tomorrowio secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TomorrowIO,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tomorrowio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TomorrowIO,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TomorrowIO.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TomorrowIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tomorrowio/tomorrowio_test.go b/pkg/detectors/tomorrowio/tomorrowio_test.go
deleted file mode 100644
index 6e5a0092ef75..000000000000
--- a/pkg/detectors/tomorrowio/tomorrowio_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tomorrowio
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "Ha1FtSsu5YhtFJixA3j5cIzgooDbVzz1"
- invalidPattern = "Ha1FtSsu5YhtFJix?3j5cIzgooDbVzz1"
- keyword = "tomorrowio"
-)
-
-func TestTomorrowIO_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tomorrowio",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tomtom/tomtom.go b/pkg/detectors/tomtom/tomtom.go
deleted file mode 100644
index f606aa0f8eee..000000000000
--- a/pkg/detectors/tomtom/tomtom.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package tomtom
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tomtom"}) + `\b([0-9Aa-zA-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"tomtom"}
-}
-
-// FromData will find and optionally verify Tomtom secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Tomtom,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.tomtom.com/map/1/tile/basic/main/0/0/0.png?view=Unified&key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Tomtom
-}
-
-func (s Scanner) Description() string {
- return "TomTom provides mapping and location technologies. TomTom API keys can be used to access and manipulate mapping data."
-}
diff --git a/pkg/detectors/tomtom/tomtom_integration_test.go b/pkg/detectors/tomtom/tomtom_integration_test.go
deleted file mode 100644
index 2a5c854124a2..000000000000
--- a/pkg/detectors/tomtom/tomtom_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tomtom
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTomtom_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TOMTOM")
- inactiveSecret := testSecrets.MustGetField("TOMTOM_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tomtom secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tomtom,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tomtom secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tomtom,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tomtom.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Tomtom.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tomtom/tomtom_test.go b/pkg/detectors/tomtom/tomtom_test.go
deleted file mode 100644
index 262509262332..000000000000
--- a/pkg/detectors/tomtom/tomtom_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tomtom
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "M9g89QjhYpjXUjMwHEUNLz1C9t58osdF"
- invalidPattern = "M9g89QjhYpjXUjMw?EUNLz1C9t58osdF"
- keyword = "tomtom"
-)
-
-func TestTomtom_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tomtom",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tradier/tradier.go b/pkg/detectors/tradier/tradier.go
deleted file mode 100644
index e32f44ba0345..000000000000
--- a/pkg/detectors/tradier/tradier.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package tradier
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tradier"}) + `\b([a-zA-Z0-9]{28})\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{"tradier"}
-}
-
-// FromData will find and optionally verify Tradier secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Tradier,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.tradier.com/v1/watchlists", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Tradier
-}
-
-func (s Scanner) Description() string {
- return "Tradier is a financial services provider that offers a trading platform and API. Tradier API keys can be used to access and manage trading accounts and execute trades."
-}
diff --git a/pkg/detectors/tradier/tradier_integration_test.go b/pkg/detectors/tradier/tradier_integration_test.go
deleted file mode 100644
index 5c5842f7aa57..000000000000
--- a/pkg/detectors/tradier/tradier_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tradier
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTradier_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TRADIER")
- inactiveSecret := testSecrets.MustGetField("TRADIER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tradier secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tradier,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tradier secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tradier,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tradier.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Tradier.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tradier/tradier_test.go b/pkg/detectors/tradier/tradier_test.go
deleted file mode 100644
index e1331147ef6d..000000000000
--- a/pkg/detectors/tradier/tradier_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tradier
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "RgH1pJ0XZKv9jh5UXHP1qGDsAxaw"
- invalidPattern = "RgH1pJ0XZKv9jh?UXHP1qGDsAxaw"
- keyword = "tradier"
-)
-
-func TestTradier_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tradier",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/transferwise/transferwise.go b/pkg/detectors/transferwise/transferwise.go
deleted file mode 100644
index af36aa3d0dd9..000000000000
--- a/pkg/detectors/transferwise/transferwise.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package transferwise
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"transferwise"}) + `\b([0-9a-f-]{8}-[0-9a-f-]{4}-[0-9a-f-]{4}-[0-9a-f-]{4}-[0-9a-f-]{12})\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{"transferwise"}
-}
-
-// FromData will find and optionally verify Transferwise secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Transferwise,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.transferwise.com/v2/profiles", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.transferwise+json; version=3")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Transferwise
-}
-
-func (s Scanner) Description() string {
- return "TransferWise (now known as Wise) is an international money transfer service. TransferWise API keys can be used to access and manage money transfers."
-}
diff --git a/pkg/detectors/transferwise/transferwise_integration_test.go b/pkg/detectors/transferwise/transferwise_integration_test.go
deleted file mode 100644
index fd833fae22fa..000000000000
--- a/pkg/detectors/transferwise/transferwise_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package transferwise
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTransferwise_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TRANSFERWISE")
- inactiveSecret := testSecrets.MustGetField("TRANSFERWISE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a transferwise secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Transferwise,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a transferwise secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Transferwise,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Transferwise.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Transferwise.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/transferwise/transferwise_test.go b/pkg/detectors/transferwise/transferwise_test.go
deleted file mode 100644
index a7681225ebf6..000000000000
--- a/pkg/detectors/transferwise/transferwise_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package transferwise
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "e99848c6-aeb1-e339-935d-fa7e0d0-4160"
- invalidPattern = "e99848c6?aeb1-e339-935d-fa7e0d0-4160"
- keyword = "transferwise"
-)
-
-func TestTransferwise_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword transferwise",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/travelpayouts/travelpayouts.go b/pkg/detectors/travelpayouts/travelpayouts.go
deleted file mode 100644
index d335011517af..000000000000
--- a/pkg/detectors/travelpayouts/travelpayouts.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package travelpayouts
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"travelpayouts"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"travelpayouts"}
-}
-
-// FromData will find and optionally verify TravelPayouts secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TravelPayouts,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.travelpayouts.com/v2/prices/latest?currency=usd&limit=5&token=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TravelPayouts
-}
-
-func (s Scanner) Description() string {
- return "TravelPayouts is a travel affiliate network that provides access to various travel-related APIs. TravelPayouts keys can be used to interact with these APIs to retrieve travel data and earn commissions on bookings."
-}
diff --git a/pkg/detectors/travelpayouts/travelpayouts_integration_test.go b/pkg/detectors/travelpayouts/travelpayouts_integration_test.go
deleted file mode 100644
index 3d057f1986b0..000000000000
--- a/pkg/detectors/travelpayouts/travelpayouts_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package travelpayouts
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTravelPayouts_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TRAVELPAYOUTS")
- inactiveSecret := testSecrets.MustGetField("TRAVELPAYOUTS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a travelpayouts secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TravelPayouts,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a travelpayouts secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TravelPayouts,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TravelPayouts.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TravelPayouts.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/travelpayouts/travelpayouts_test.go b/pkg/detectors/travelpayouts/travelpayouts_test.go
deleted file mode 100644
index 871ffdb8d99e..000000000000
--- a/pkg/detectors/travelpayouts/travelpayouts_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package travelpayouts
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "au3cjfqsfb1wtolub0py5cayxliubnxg"
- invalidPattern = "au3cjfqsf?1wtolub0py5cayxliubnxg"
- keyword = "travelpayouts"
-)
-
-func TestTravelPayouts_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword travelpayouts",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/travisci/travisci.go b/pkg/detectors/travisci/travisci.go
deleted file mode 100644
index 56972ea96411..000000000000
--- a/pkg/detectors/travisci/travisci.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package travisci
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"travis"}) + `\b([a-zA-Z0-9A-Z_]{22})\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{"travis"}
-}
-
-// FromData will find and optionally verify TravisCI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TravisCI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.travis-ci.com/user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("token %s", resMatch))
- req.Header.Add("User-Agent", "API Explorer")
- req.Header.Add("Travis-API-Version", "3")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TravisCI
-}
-
-func (s Scanner) Description() string {
- return "Travis CI is a continuous integration service used to build and test software projects hosted on GitHub and Bitbucket. Travis CI tokens can be used to interact with the Travis CI API."
-}
diff --git a/pkg/detectors/travisci/travisci_integration_test.go b/pkg/detectors/travisci/travisci_integration_test.go
deleted file mode 100644
index 77e9fc0bd320..000000000000
--- a/pkg/detectors/travisci/travisci_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package travisci
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTravisCI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TRAVISCI_TOKEN")
- inactiveSecret := testSecrets.MustGetField("TRAVISCI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a travisci secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TravisCI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a travisci secret %s within but unverified", inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TravisCI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TravisCI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TravisCI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/travisci/travisci_test.go b/pkg/detectors/travisci/travisci_test.go
deleted file mode 100644
index 1c140576904b..000000000000
--- a/pkg/detectors/travisci/travisci_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package travisci
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "M1BeOsJ75okEpkQUqupx0Q"
- invalidPattern = "M1BeOsJ75ok?pkQUqupx0Q"
- keyword = "travisci"
-)
-
-func TestTravisCI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword travisci",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/trelloapikey/trelloapikey.go b/pkg/detectors/trelloapikey/trelloapikey.go
deleted file mode 100644
index 0e67c4ada01c..000000000000
--- a/pkg/detectors/trelloapikey/trelloapikey.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package trelloapikey
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
- tokenPat = regexp.MustCompile(`\b([a-zA-Z-0-9]{64})\b`)
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"trello"}) + `\b([a-zA-Z-0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"trello"}
-}
-
-// FromData will find and optionally verify TrelloApiKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- tokenMatches := tokenPat.FindAllStringSubmatch(dataStr, -1)
-
- for i, match := range matches {
- if i == 0 {
- resMatch := strings.TrimSpace(match[1])
- for _, tokenMatch := range tokenMatches {
-
- token := strings.TrimSpace(tokenMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TrelloApiKey,
- Redacted: resMatch,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.trello.com/1/members/me?key="+resMatch+"&token="+token, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
- results = append(results, s1)
-
- }
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TrelloApiKey
-}
-
-func (s Scanner) Description() string {
- return "Trello is a collaboration tool that organizes your projects into boards. Trello API keys can be used to access and modify data within Trello."
-}
diff --git a/pkg/detectors/trelloapikey/trelloapikey_integration_test.go b/pkg/detectors/trelloapikey/trelloapikey_integration_test.go
deleted file mode 100644
index b2aff40aafd0..000000000000
--- a/pkg/detectors/trelloapikey/trelloapikey_integration_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package trelloapikey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTrelloApiKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- token := testSecrets.MustGetField("TRELLOAPIKEY_TOKEN")
- apiKey := testSecrets.MustGetField("TRELLO_API_KEY")
- inactiveApiKey := testSecrets.MustGetField("TRELLO_API_KEY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a trello secret within https://api.trello.com/1/members/me?key=%s&token=%s", apiKey, token)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TrelloApiKey,
- Redacted: apiKey,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a trello secret within https://api.trello.com/1/members/me?key=%s&token=%s but unverified", inactiveApiKey, token)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TrelloApiKey,
- Redacted: "6abe2cb200b2edy1666avq5325476dfp",
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TrelloApiKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TrelloApiKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/trelloapikey/trelloapikey_test.go b/pkg/detectors/trelloapikey/trelloapikey_test.go
deleted file mode 100644
index 05327475cfdf..000000000000
--- a/pkg/detectors/trelloapikey/trelloapikey_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package trelloapikey
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validToken = "Zqpox-FOtGtvV58NCfjox3y2KROmjsbvAUW2jLSL9XfPFs3P98BwmjzMKJA1KVPa"
- invalidToken = "Zqpox-FOtGtvV58NCfjox3y2KROmjsbv?UW2jLSL9XfPFs3P98BwmjzMKJA1KVPa"
- validKey = "NKVmEKtpJvQXU95OfApuFxCOmaRLcRsc"
- invalidKey = "NKVmEKtpJvQXU95O?ApuFxCOmaRLcRsc"
- keyword = "trelloapikey"
-)
-
-func TestTrelloApiKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword trelloapikey",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validToken, keyword, validKey),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidToken, keyword, invalidKey),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tru/tru.go b/pkg/detectors/tru/tru.go
deleted file mode 100644
index 43ccfcb9edca..000000000000
--- a/pkg/detectors/tru/tru.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package tru
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tru"}) + `\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\b`)
- secrePat = regexp.MustCompile(detectors.PrefixRegex([]string{"tru"}) + `\b([0-9a-zA-Z.-_]{26})\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{"tru"}
-}
-
-// FromData will find and optionally verify Tru secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secrePat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, secretMatch := range secretMatches {
- resSecret := strings.TrimSpace(secretMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Tru,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + resSecret),
- }
-
- if verify {
- data := fmt.Sprintf("%s:%s", resMatch, resSecret)
- baseToken := b64.StdEncoding.EncodeToString([]byte(data))
- payload := strings.NewReader("grant_type=client_credentials")
- req, err := http.NewRequestWithContext(ctx, "POST", "https://eu.api.tru.id/oauth2/v1/token", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", baseToken))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Tru
-}
-
-func (s Scanner) Description() string {
- return "Tru is a service that provides APIs for phone number verification and other identity verification services. Tru credentials can be used to access these APIs and perform verification operations."
-}
diff --git a/pkg/detectors/tru/tru_integration_test.go b/pkg/detectors/tru/tru_integration_test.go
deleted file mode 100644
index 17d56acf6bee..000000000000
--- a/pkg/detectors/tru/tru_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tru
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTru_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- id := testSecrets.MustGetField("TRU_ID")
- secret := testSecrets.MustGetField("TRU_SECRET")
- inactiveID := testSecrets.MustGetField("TRU_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tru secret %s within tru id %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tru,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tru secret %s within tru id %s but not valid", secret, inactiveID)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tru,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tru.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Tru.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tru/tru_test.go b/pkg/detectors/tru/tru_test.go
deleted file mode 100644
index 1cfa21e506ec..000000000000
--- a/pkg/detectors/tru/tru_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package tru
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "o23j7m2a-23ux-0hnl-0k57-4ph2omvcanxg"
- invalidKey = "o23j7m2a?23ux-0hnl-0k57-4ph2omvcanxg"
- validSecret = "PJ]IEWcY9HOIqDGfUqlFGRL1En"
- invalidSecret = "PJ]IEWcY9HOIq!GfUqlFGRL1En"
- keyword = "tru"
-)
-
-func TestTru_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tru",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validSecret),
- want: []string{validKey + validSecret},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidSecret),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/trufflehogenterprise/trufflehogenterprise.go b/pkg/detectors/trufflehogenterprise/trufflehogenterprise.go
deleted file mode 100644
index ae44d15a262f..000000000000
--- a/pkg/detectors/trufflehogenterprise/trufflehogenterprise.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package trufflehogenterprise
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\bthog-key-[0-9a-f]{16}\b`)
- secretPat = regexp.MustCompile(`\bthog-secret-[0-9a-f]{32}\b`)
- hostnamePat = regexp.MustCompile(`\b[a-z]+-[a-z]+-[a-z]+\.[a-z][0-9]\.[a-z]+\.trufflehog\.org\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{"thog"}
-}
-
-// FromData will find and optionally verify TruffleHog Enterprise secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)
- secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
- hostnameMatches := hostnamePat.FindAllStringSubmatch(dataStr, -1)
-
- for _, keyMatch := range keyMatches {
- resKeyMatch := strings.TrimSpace(keyMatch[0])
- for _, secretMatch := range secretMatches {
- resSecretMatch := strings.TrimSpace(secretMatch[0])
-
- for _, hostnameMatch := range hostnameMatches {
-
- resHostnameMatch := strings.TrimSpace(hostnameMatch[0])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TrufflehogEnterprise,
- Raw: []byte(resKeyMatch),
- }
-
- if verify {
- endpoint := fmt.Sprintf("https://%s/api/v1/sources", resHostnameMatch)
- req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil)
- if err != nil {
- continue
- }
-
- req.Header.Add("Accept", "application/vnd.trufflehogenterprise+json; version=3")
- req.Header.Add("X-Thog-Secret", resSecretMatch)
- req.Header.Add("X-Thog-Key", resKeyMatch)
-
- res, err := client.Do(req)
- if err == nil {
- verifiedBodyResponse, err := common.ResponseContainsSubstring(res.Body, "data")
- if err != nil {
- return nil, err
- }
-
- defer res.Body.Close()
-
- if res.StatusCode >= 200 && res.StatusCode < 300 && verifiedBodyResponse {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TrufflehogEnterprise
-}
-
-func (s Scanner) Description() string {
- return "TruffleHog Enterprise is a tool for detecting and verifying secrets in your codebase. The keys and secrets detected can be used to access TruffleHog Enterprise services."
-}
diff --git a/pkg/detectors/trufflehogenterprise/trufflehogenterprise_integration_test.go b/pkg/detectors/trufflehogenterprise/trufflehogenterprise_integration_test.go
deleted file mode 100644
index 56e10f2b9416..000000000000
--- a/pkg/detectors/trufflehogenterprise/trufflehogenterprise_integration_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package trufflehogenterprise
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTrufflehogenterprise_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- host := testSecrets.MustGetField("THOG_HOSTNAME")
- key := testSecrets.MustGetField("THOG_WEB_KEY")
- secret := testSecrets.MustGetField("THOG_WEB_SECRET")
- inactiveSecret := testSecrets.MustGetField("THOG_WEB_INACTIVE_SECRET")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a thog secret %s for %s with key %s within", secret, host, key)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TrufflehogEnterprise,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a trufflehogenterprise secret %s for %s with key %s within but not valid", inactiveSecret, host, key)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TrufflehogEnterprise,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Trufflehogenterprise.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Trufflehogenterprise.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/trufflehogenterprise/trufflehogenterprise_test.go b/pkg/detectors/trufflehogenterprise/trufflehogenterprise_test.go
deleted file mode 100644
index 0e9d64db72ed..000000000000
--- a/pkg/detectors/trufflehogenterprise/trufflehogenterprise_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package trufflehogenterprise
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "thog-key-946c7e0fbee2baff"
- invalidKey = "thog-key-946?7e0fbee2baff"
- validSecret = "thog-secret-8fbfc135085421e62d8f1982af17bbf6"
- invalidSecret = "thog-secret-8fbfc13?085421e62d8f1982af17bbf6"
- validHostname = "uryfanaextzftqnwkordjwtrascqbihyctwttsntssxgbmgtnghmaiossoiablwqntsnudfdz-grthynfwdntsfdyuvk-tqfecqndhkmebecezcyzptxnsgprzkdcwzwnzdxpm.v6.zfjkrzjmutvvwwqftipvtkdwg.trufflehog.org"
- invalidHostname = "?ryfanaextzftqnwkordjwtrascqbihyctwttsntssxgbmgtnghmaiossoiablwqntsnudfdz-grthynfwdntsfdyuvk-tqfecqndhkmebecezcyzptxnsgprzkdcwzwnzdxpm.v6.zfjkrzjmutvvwwqftipvtkdwg.trufflehog.org"
- keyword = "trufflehogenterprise"
-)
-
-func TestTrufflehogenterprise_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword trufflehogenterprise",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validSecret, keyword, validHostname),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidSecret, keyword, invalidHostname),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/twelvedata/twelvedata.go b/pkg/detectors/twelvedata/twelvedata.go
deleted file mode 100644
index afc2d50b9a86..000000000000
--- a/pkg/detectors/twelvedata/twelvedata.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package twelvedata
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"twelvedata"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"twelvedata"}
-}
-
-// FromData will find and optionally verify TwelveData secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TwelveData,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.twelvedata.com/earliest_timestamp?symbol=AAPL&interval=1day&apikey="+resMatch, nil)
- if err != nil {
- continue
- }
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
-
- // if client_id and client_secret is valid -> 403 {"error":"invalid_grant","error_description":"Invalid authorization code"}
- // if invalid -> 401 {"error":"access_denied","error_description":"Unauthorized"}
- // ingenious!
-
- if !strings.Contains(body, "401") {
- s1.Verified = true
- }
-
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TwelveData
-}
-
-func (s Scanner) Description() string {
- return "TwelveData provides financial data APIs for stock, forex, cryptocurrency, and more. TwelveData API keys can be used to access and retrieve this financial data."
-}
diff --git a/pkg/detectors/twelvedata/twelvedata_integration_test.go b/pkg/detectors/twelvedata/twelvedata_integration_test.go
deleted file mode 100644
index f46b777a429d..000000000000
--- a/pkg/detectors/twelvedata/twelvedata_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package twelvedata
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTwelveData_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TWELVEDATA_TOKEN")
- inactiveSecret := testSecrets.MustGetField("TWELVEDATA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twelvedata secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TwelveData,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twelvedata secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TwelveData,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TwelveData.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("TwelveData.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/twelvedata/twelvedata_test.go b/pkg/detectors/twelvedata/twelvedata_test.go
deleted file mode 100644
index 32479732b097..000000000000
--- a/pkg/detectors/twelvedata/twelvedata_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package twelvedata
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "8qevq8517nx58ja9e7jrhe4uom3qbo3v"
- invalidPattern = "8q?vq8517nx58ja9e7jrhe4uom3qbo3v"
- keyword = "twelvedata"
-)
-
-func TestTwelveData_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword twelvedata",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/twilio/twilio.go b/pkg/detectors/twilio/twilio.go
deleted file mode 100644
index ca01b94d2755..000000000000
--- a/pkg/detectors/twilio/twilio.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package twilio
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.RetryableHTTPClient()
- sidPat = regexp.MustCompile(`\bAC[0-9a-f]{32}\b`)
- keyPat = regexp.MustCompile(`\b[0-9a-f]{32}\b`)
-)
-
-type serviceResponse struct {
- Services []service `json:"services"`
-}
-
-type service struct {
- FriendlyName string `json:"friendly_name"` // friendly name of a service
- SID string `json:"sid"` // object id of service
- AccountSID string `json:"account_sid"` // account sid
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
-
- return defaultClient
-}
-
-// 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{"sid", "twilio"}
-}
-
-// FromData will find and optionally verify Twilio secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keyMatches := keyPat.FindAllString(dataStr, -1)
- sidMatches := sidPat.FindAllString(dataStr, -1)
-
- for _, sid := range sidMatches {
- for _, key := range keyMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Twilio,
- Raw: []byte(sid),
- RawV2: []byte(sid + key),
- Redacted: sid,
- }
-
- s1.ExtraData = map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/twilio/",
- }
-
- if verify {
- extraData, isVerified, verificationErr := verifyTwilio(ctx, s.getClient(), key, sid)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
-
- for key, value := range extraData {
- s1.ExtraData[key] = value
- }
-
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{"key": key, "sid": sid}
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Twilio
-}
-
-func (s Scanner) Description() string {
- return "Twilio is a cloud communications platform that allows software developers to programmatically make and receive phone calls, send and receive text messages, and perform other communication functions using its web service APIs."
-}
-
-func verifyTwilio(ctx context.Context, client *http.Client, key, sid string) (map[string]string, bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://verify.twilio.com/v2/Services", nil)
- if err != nil {
- return nil, false, nil
- }
-
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Add("Accept", "*/*")
- req.SetBasicAuth(sid, key)
- resp, err := client.Do(req)
- if err != nil {
- return nil, false, nil
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- extraData := make(map[string]string)
- var serviceResponse serviceResponse
-
- if err := json.NewDecoder(resp.Body).Decode(&serviceResponse); err == nil && len(serviceResponse.Services) > 0 { // no error in parsing and have at least one service
- service := serviceResponse.Services[0]
- extraData["friendly_name"] = service.FriendlyName
- extraData["account_sid"] = service.AccountSID
- }
-
- return extraData, true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil, false, nil
- default:
- return nil, false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/twilio/twilio_integration_test.go b/pkg/detectors/twilio/twilio_integration_test.go
deleted file mode 100644
index 246fd7fcd9fa..000000000000
--- a/pkg/detectors/twilio/twilio_integration_test.go
+++ /dev/null
@@ -1,181 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package twilio
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTwilio_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TWILLIO_API")
- secretInactive := testSecrets.MustGetField("TWILLIO_API_INACTIVE")
- id := testSecrets.MustGetField("TWILLIO_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twillio secret %s within awsId %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twilio,
- Verified: true,
- Redacted: id,
- RawV2: []byte(id + secret),
- ExtraData: map[string]string{
- "account_sid": "ACa5b6165773490f33f226d71e7ffacff5",
- "friendly_name": "MyServiceName",
- "rotation_guide": "https://howtorotate.com/docs/tutorials/twilio/",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, not verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twillio secret %s within awsId %s", secretInactive, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twilio,
- Verified: false,
- Redacted: id,
- RawV2: []byte(id + secretInactive),
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/twilio/",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(100 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twillio secret %s within awsId %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twilio,
- Verified: false,
- Redacted: id,
- RawV2: []byte(id + secret),
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/twilio/",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twillio secret %s within awsId %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twilio,
- Verified: false,
- Redacted: id,
- RawV2: []byte(id + secret),
- ExtraData: map[string]string{
- "rotation_guide": "https://howtorotate.com/docs/tutorials/twilio/",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Twilio.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Twilio.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/twilio/twilio_test.go b/pkg/detectors/twilio/twilio_test.go
deleted file mode 100644
index a7f30df60f67..000000000000
--- a/pkg/detectors/twilio/twilio_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package twilio
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validSid = "AC1b3f0bddbb6887d68d8454e66c749c6a"
- invalidSid = "AC1b3f0bddbb?887d68d8454e66c749c6a"
- validKey = "daf7b3d34b9787f1212316eea62ba186"
- invalidKey = "daf7b3d34b9787f1?12316eea62ba186"
- keyword = "twilio"
-)
-
-func TestTwilio_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword twilio",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validSid, keyword, validKey),
- want: []string{validSid + validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidSid, keyword, invalidKey),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/twilioapikey/twilioapikey.go b/pkg/detectors/twilioapikey/twilioapikey.go
deleted file mode 100644
index 677e4ceb3c8e..000000000000
--- a/pkg/detectors/twilioapikey/twilioapikey.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package twilioapikey
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- apiKeyPat = regexp.MustCompile(`\bSK[a-zA-Z0-9]{32}\b`)
- secretPat = regexp.MustCompile(`\b[0-9a-zA-Z]{32}\b`)
-)
-
-type serviceResponse struct {
- Services []struct {
- FriendlyName string `json:"friendly_name"` // friendly name of a service
- SID string `json:"sid"` // object id of service
- AccountSID string `json:"account_sid"` // account sid
- } `json:"services"`
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
-
- return defaultClient
-}
-
-// 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{"twilio"}
-}
-
-// FromData will find and optionally verify Twilio secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- apiKeyMatches := apiKeyPat.FindAllString(dataStr, -1)
- secretMatches := secretPat.FindAllString(dataStr, -1)
-
- for _, apiKey := range apiKeyMatches {
- for _, secret := range secretMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Twilio,
- Raw: []byte(apiKey),
- RawV2: []byte(apiKey + secret),
- Redacted: secret[:5] + "...",
- ExtraData: make(map[string]string),
- }
-
- if verify {
- extraData, isVerified, verificationErr := verifyTwilioAPIKey(ctx, s.getClient(), apiKey, secret)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
-
- for key, value := range extraData {
- s1.ExtraData[key] = value
- }
-
- if s1.Verified {
- s1.AnalysisInfo = map[string]string{"key": apiKey, "sid": secret}
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TwilioApiKey
-}
-
-func (s Scanner) Description() string {
- return "Twilio is a cloud communications platform that allows software developers to programmatically make and receive phone calls, send and receive text messages, and perform other communication functions using its web service APIs."
-}
-
-func verifyTwilioAPIKey(ctx context.Context, client *http.Client, apiKey, secret string) (map[string]string, bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://verify.twilio.com/v2/Services", nil)
- if err != nil {
- return nil, false, nil
- }
-
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Add("Accept", "*/*")
- req.SetBasicAuth(apiKey, secret)
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, false, nil
- }
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- extraData := make(map[string]string)
- var serviceResponse serviceResponse
-
- if err := json.NewDecoder(resp.Body).Decode(&serviceResponse); err == nil && len(serviceResponse.Services) > 0 { // no error in parsing and have at least one service
- service := serviceResponse.Services[0]
- extraData["friendly_name"] = service.FriendlyName
- extraData["account_sid"] = service.AccountSID
- }
-
- return extraData, true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return nil, false, nil
- default:
- return nil, false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/twilioapikey/twilioapikey_integration_test.go b/pkg/detectors/twilioapikey/twilioapikey_integration_test.go
deleted file mode 100644
index 0f506bd7d958..000000000000
--- a/pkg/detectors/twilioapikey/twilioapikey_integration_test.go
+++ /dev/null
@@ -1,135 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package twilioapikey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTwilioAPIKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
-
- secret := testSecrets.MustGetField("TWILLIO_SECRET")
- secretInactive := testSecrets.MustGetField("TWILLIO_SECRET_INACTIVE")
- apiKey := testSecrets.MustGetField("TWILLIO_APIKEY")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twillio secret %s within awsId %s", secret, apiKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twilio,
- Verified: true,
- Redacted: secret,
- RawV2: []byte(apiKey + secret),
- ExtraData: map[string]string{
- "account_sid": "ACa5b6165773490f33f226d71e7ffacff5",
- "friendly_name": "MyServiceName",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, not verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twillio secret %s within awsId %s", secretInactive, apiKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twilio,
- Verified: false,
- Redacted: secretInactive,
- RawV2: []byte(apiKey + secretInactive),
- ExtraData: map[string]string{},
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Twilio.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Twilio.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/twilioapikey/twilioapikey_test.go b/pkg/detectors/twilioapikey/twilioapikey_test.go
deleted file mode 100644
index 4e61f3462788..000000000000
--- a/pkg/detectors/twilioapikey/twilioapikey_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package twilioapikey
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validAPIKey = "SKbddcfb88fake8f7c4d9aefake7de1fc5"
- invalidAPIKey = "SK_bddcfb88fake8f7c4d9aefake7de1fc"
- validSecret = "k7JXtY3WBtUqthisisfakeZDqVcjZxYI"
- invalidSecret = "k6JXtY3WBtU$thisisfakeZDqVcjZxYI"
- keyword = "twilio"
-)
-
-func TestTwilioAPIKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword twilio",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validAPIKey, keyword, validSecret),
- want: []string{validAPIKey + validSecret},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidAPIKey, keyword, invalidSecret),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
-
-func TestTwilioAPIKey_SecretRedacted(t *testing.T) {
- d := Scanner{}
-
- results, err := d.FromData(
- context.Background(),
- false,
- []byte(fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validAPIKey, keyword, validSecret)))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) == 0 {
- t.Errorf("did not receive result")
- }
-
- if results[0].Redacted != validSecret[:5]+"..." {
- t.Errorf("expected redacted secret to be '%s', got '%s'", validSecret[:5]+"...", results[0].Redacted)
- }
-}
diff --git a/pkg/detectors/twist/twist.go b/pkg/detectors/twist/twist.go
deleted file mode 100644
index 819c3eff5c7e..000000000000
--- a/pkg/detectors/twist/twist.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package twist
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- accessToken = regexp.MustCompile(detectors.PrefixRegex([]string{"twist"}) + `\b(?:oauth2:)?([0-9a-f]{40})\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{"twist"}
-}
-
-// FromData will find and optionally verify Twist secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := accessToken.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- setAuth := resMatch
-
- if strings.Contains(match[0], "oauth") {
- setAuth = fmt.Sprintf("oauth2:%s", strings.TrimSpace(match[1]))
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Twist,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.twist.com/api/v3/users/get_session_user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", setAuth))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
- results = append(results, s1)
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Twist
-}
-
-func (s Scanner) Description() string {
- return "Twist is a team communication app. Twist access tokens can be used to access and manage Twist accounts and data."
-}
diff --git a/pkg/detectors/twist/twist_integration_test.go b/pkg/detectors/twist/twist_integration_test.go
deleted file mode 100644
index ff3fc753858d..000000000000
--- a/pkg/detectors/twist/twist_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package twist
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTwist_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TWIST")
- inactiveSecret := testSecrets.MustGetField("TWIST_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twist secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twist,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twist secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twist,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Twist.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Twist.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/twist/twist_test.go b/pkg/detectors/twist/twist_test.go
deleted file mode 100644
index d09c71ded091..000000000000
--- a/pkg/detectors/twist/twist_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package twist
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- prefix = "oauth2:"
- validPattern = "dba06b506389c94f2bfc010fa83690cf7171dd40"
- invalidPattern = "dga06b506389c94f2bfc010fa83690cf7171dd40"
- keyword = "twist"
-)
-
-func TestTwist_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword twist",
- input: fmt.Sprintf("%s token = '%s%s'", keyword, prefix, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s%s' | '%s%s'", keyword, prefix, validPattern, prefix, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s%s'", keyword, prefix, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s%s'", keyword, prefix, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/twitch/twitch.go b/pkg/detectors/twitch/twitch.go
deleted file mode 100644
index e8171befff07..000000000000
--- a/pkg/detectors/twitch/twitch.go
+++ /dev/null
@@ -1,125 +0,0 @@
-package twitch
-
-import (
- "context"
- "fmt"
- "net/http"
- "net/url"
- "strconv"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-const verifyURL = "https://id.twitch.tv/oauth2/token"
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"twitch"}) + `\b([0-9a-z]{30})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"twitch"}) + `\b([0-9a-z]{30})\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{"twitch"}
-}
-
-// FromData will find and optionally verify Twitch secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueIDMatches, uniqueSecretMatches = make(map[string]struct{}), make(map[string]struct{})
-
- for _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueIDMatches[match[1]] = struct{}{}
- }
-
- for _, match := range secretPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueSecretMatches[match[1]] = struct{}{}
- }
-
- for id := range uniqueIDMatches {
- for secret := range uniqueSecretMatches {
- // as both patterns are same, to avoid same strings
- if id == secret {
- continue
- }
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Twitch,
- Raw: []byte(id),
- RawV2: []byte(id + ":" + secret),
- }
-
- if verify {
- client := s.getClient()
- isVerified, verificationErr := verifyTwitch(ctx, client, secret, id)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, id)
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
- return defaultClient
-}
-
-func verifyTwitch(ctx context.Context, client *http.Client, resMatch string, resIdMatch string) (bool, error) {
- data := url.Values{}
- data.Set("client_id", resIdMatch)
- data.Set("client_secret", resMatch)
- data.Set("grant_type", "client_credentials")
- encodedData := data.Encode()
-
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, verifyURL, strings.NewReader(encodedData))
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer res.Body.Close()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusBadRequest, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected http response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Twitch
-}
-
-func (s Scanner) Description() string {
- return "Twitch is a live streaming service. Twitch client credentials can be used to access and modify data on the Twitch platform."
-}
diff --git a/pkg/detectors/twitch/twitch_integration_test.go b/pkg/detectors/twitch/twitch_integration_test.go
deleted file mode 100644
index ea8b39b712d4..000000000000
--- a/pkg/detectors/twitch/twitch_integration_test.go
+++ /dev/null
@@ -1,178 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package twitch
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTwitch_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TWITCH")
- id := testSecrets.MustGetField("TWITCH_ID")
- inactiveSecret := testSecrets.MustGetField("TWITCH_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitch secret %s within twitch %s", secret, id)),
- verify: true,
- },
- // the detector will try every combination of the secret and id for
- // client_id and client_secret, so we expect 4 results
- // but only 1 of them will be verified
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twitch,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Twitch,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitch secret %s within twitch %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twitch,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Twitch,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitch secret %s within twitch %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twitch,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Twitch,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitch secret %s within twitch %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twitch,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Twitch,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Twitch.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Errorf("Twitch.FromData() verificationError = %v, wantVerificationErr %v", got[i].VerificationError(), tt.wantVerificationErr)
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Twitch.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/twitch/twitch_test.go b/pkg/detectors/twitch/twitch_test.go
deleted file mode 100644
index 29f0bd9dd360..000000000000
--- a/pkg/detectors/twitch/twitch_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package twitch
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "k0lj2mwl8n3ztmrivlpbc7obk914sj"
- invalidKey = "k0lj2mwl8n3ztmr?vlpbc7obk914sj"
- validId = "kn64qw9jt39bhni04h2k5jc7ebefyn"
- invalidId = "kn64qw9jt39bhni?4h2k5jc7ebefyn"
- keyword = "twitch"
-)
-
-func TestTwitch_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword twitch",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validId + ":" + validKey, validKey + ":" + validId},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/twitchaccesstoken/twitchaccesstoken.go b/pkg/detectors/twitchaccesstoken/twitchaccesstoken.go
deleted file mode 100644
index c1e99ee31941..000000000000
--- a/pkg/detectors/twitchaccesstoken/twitchaccesstoken.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package twitchaccesstoken
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"twitch"}) + `\b([0-9a-z]{30})\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{"twitch"}
-}
-
-// FromData will find and optionally verify Twitchaccesstoken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TwitchAccessToken,
- Raw: []byte(match),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://id.twitch.tv/oauth2/validate", nil)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("OAuth %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- // If the endpoint returns useful information, we can return it as a map.
- return true, nil, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TwitchAccessToken
-}
-
-func (s Scanner) Description() string {
- return "Twitch is a live streaming service. Twitch access tokens can be used to access and modify data on the Twitch platform."
-}
diff --git a/pkg/detectors/twitchaccesstoken/twitchaccesstoken_integration_test.go b/pkg/detectors/twitchaccesstoken/twitchaccesstoken_integration_test.go
deleted file mode 100644
index 819d6d985b47..000000000000
--- a/pkg/detectors/twitchaccesstoken/twitchaccesstoken_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package twitchaccesstoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTwitchaccesstoken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TWITCHACCESSTOKEN")
- inactiveSecret := testSecrets.MustGetField("TWITCHACCESSTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitchaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TwitchAccessToken,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitchaccesstoken secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TwitchAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitchaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TwitchAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitchaccesstoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TwitchAccessToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Twitchaccesstoken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Twitchaccesstoken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/twitchaccesstoken/twitchaccesstoken_test.go b/pkg/detectors/twitchaccesstoken/twitchaccesstoken_test.go
deleted file mode 100644
index 9250ee2a3094..000000000000
--- a/pkg/detectors/twitchaccesstoken/twitchaccesstoken_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package twitchaccesstoken
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestTwitchaccesstoken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: "twitchaccesstoken_token = 'abc123def456ghi789jkl012mno345'",
- want: []string{"abc123def456ghi789jkl012mno345"},
- },
- {
- name: "env variable pattern",
- input: "'twitch_access_token': 'abc123def456ghi789jkl012mno345'",
- want: []string{"abc123def456ghi789jkl012mno345"},
- },
- {
- name: "get request pattern - keyword out of range",
- input: "curl -X GET 'https://id.twitch.tv/oauth2/validate' -H 'Authorization: OAuth xbc123def456ghi789jkl012mno345'",
- want: []string{},
- },
- {
- name: "finds all matches",
- input: "twitchaccesstoken_token1 = 'z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5' twitchaccesstoken_token2 = '123abc456def789ghi012jkl345mno'",
- want: []string{"z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5", "123abc456def789ghi012jkl345mno"},
- },
- {
- name: "invald pattern",
- input: "twitchaccesstoken_token = '1a2b3c4d'",
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/twitter/v1/twitter_v1.go b/pkg/detectors/twitter/v1/twitter_v1.go
deleted file mode 100644
index ec2d0b595fde..000000000000
--- a/pkg/detectors/twitter/v1/twitter_v1.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package twitter
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"twitter"}) + `\b([A-Z]{22}%[a-zA-Z-0-9]{23}%[a-zA-Z-0-9]{6}%[a-zA-Z-0-9]{3}%[a-zA-Z-0-9]{9}%[a-zA-Z-0-9]{52})\b`)
-)
-
-func (s Scanner) Version() int { return 1 }
-
-// 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{"twitter"}
-}
-
-// FromData will find and optionally verify Twitter secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keyMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatches[match[1]] = struct{}{}
- }
-
- for resMatch := range keyMatches {
- resMatch = strings.TrimSpace(resMatch)
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Twitter,
- Raw: []byte(resMatch),
- ExtraData: map[string]string{
- "version": fmt.Sprintf("%d", s.Version()),
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- isVerified, err := s.VerifyTwitterToken(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(err)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Twitter
-}
-
-func (s Scanner) Description() string {
- return "Twitter API keys can be used to interact with the Twitter API to post tweets, read timelines, and access other Twitter functionalities."
-}
-
-func (s Scanner) VerifyTwitterToken(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.twitter.com/2/tweets/20", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer res.Body.Close()
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
diff --git a/pkg/detectors/twitter/v1/twitter_v1_integration_test.go b/pkg/detectors/twitter/v1/twitter_v1_integration_test.go
deleted file mode 100644
index b65178082991..000000000000
--- a/pkg/detectors/twitter/v1/twitter_v1_integration_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package twitter
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTwitter_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TWITTER")
- inactiveSecret := testSecrets.MustGetField("TWITTER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitter secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twitter,
- Verified: true,
- ExtraData: map[string]string{
- "version": "1",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitter secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twitter,
- Verified: false,
- ExtraData: map[string]string{
- "version": "1",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Twitter.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Twitter.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/twitter/v1/twitter_v1_test.go b/pkg/detectors/twitter/v1/twitter_v1_test.go
deleted file mode 100644
index 4facd32740d1..000000000000
--- a/pkg/detectors/twitter/v1/twitter_v1_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package twitter
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "EGGIFBZPKVTIKCEJCSRHQY%5PabdkLS20yFOCKlOiXqbTG%PHGkqp%Ybm%2YgUAvoI-%3ZXVX8KgdsYSJ1DEgoxhGkoY6cNMN8EZidvGnJQ4Z8-4V2fSbdMU"
- invalidPattern = "EGGIFBZPKVTIKCEJCSRHQY%5PabdkLS20yFOCKlOiXqbTG%PHGkqp%Ybm%2Y?UAvoI-%3ZXVX8KgdsYSJ1DEgoxhGkoY6cNMN8EZidvGnJQ4Z8-4V2fSbdMU"
- keyword = "twitter"
-)
-
-func TestTwitter_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword twitter",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/twitter/v2/twitter_v2.go b/pkg/detectors/twitter/v2/twitter_v2.go
deleted file mode 100644
index 63db3e1a825f..000000000000
--- a/pkg/detectors/twitter/v2/twitter_v2.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package twitter
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitter/v1"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- v1.Scanner
-
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-var _ detectors.Versioner = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"twitter"}) + `\b([a-zA-Z0-9]{20,59}%([a-zA-Z0-9]{3,}%){0,2}[a-zA-Z0-9]{52})\b`)
-)
-
-func (s Scanner) Version() int { return 2 }
-
-// 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{"twitter"}
-}
-
-// FromData will find and optionally verify Twitter secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keyMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatches[match[1]] = struct{}{}
- }
-
- for match := range keyMatches {
- match = strings.TrimSpace(match)
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Twitter,
- Raw: []byte(match),
- ExtraData: map[string]string{
- "version": fmt.Sprintf("%d", s.Version()),
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- isVerified, err := s.VerifyTwitterToken(ctx, client, match)
- s1.Verified = isVerified
- s1.SetVerificationError(err)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Twitter
-}
-
-// Description returns a description for the result being detected
-func (s Scanner) Description() string {
- return "Twitter API keys can be used to access and interact with Twitter's platform programmatically."
-}
diff --git a/pkg/detectors/twitter/v2/twitter_v2_integration_test.go b/pkg/detectors/twitter/v2/twitter_v2_integration_test.go
deleted file mode 100644
index aac6f391c84f..000000000000
--- a/pkg/detectors/twitter/v2/twitter_v2_integration_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package twitter
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTwitter_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TWITTER_V2_ACTIVE")
- inactiveSecret := testSecrets.MustGetField("TWITTER_V2_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitter secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twitter,
- Verified: true,
- ExtraData: map[string]string{
- "version": "2",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitter secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Twitter,
- Verified: false,
- ExtraData: map[string]string{
- "version": "2",
- },
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Twitter.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Twitter.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/twitter/v2/twitter_v2_test.go b/pkg/detectors/twitter/v2/twitter_v2_test.go
deleted file mode 100644
index 64072786932e..000000000000
--- a/pkg/detectors/twitter/v2/twitter_v2_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package twitter
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "6pvFJmFvkNjdMImKVWF1NOZ%9n5K8rIH4JOfczZSg5uAsHErlQY6c5TrAygcZYF9WttO1m4LGhg0IUbFgXcaXtAmH4SnIb5cHac0sNcQHVKXZgaKxMqw1Fvt87RcyYKJ1bSP0BGPE%p912wZKbAkTcRfglLMyrcV6aMnuxbj7ctFoulIgFw8Aljzv87cs4"
- invalidPattern = "6pvFJmFvkNjdMImKVWF1NOZ%9n5K8rIH4JOf?zZSg5uAsHErlQY6c5TrAygcZYF9WttO1m4LGhg0IUbFgXcaXtAmH4SnIb5cHac0sNcQHVKXZgaKxMqw1Fvt87RcyYKJ1bSP0BGPE%p912wZKbAkTcRfglLMyrcV6aMnuxbj7ctFoulIgFw8Aljzv87cs4"
- keyword = "twitter"
-)
-
-func TestTwitter_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword twitter",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/twitterconsumerkey/twitterconsumerkey.go b/pkg/detectors/twitterconsumerkey/twitterconsumerkey.go
deleted file mode 100644
index 7f0a43fc75a5..000000000000
--- a/pkg/detectors/twitterconsumerkey/twitterconsumerkey.go
+++ /dev/null
@@ -1,148 +0,0 @@
-package twitterconsumerkey
-
-import (
- "context"
- b64 "encoding/base64"
- "encoding/json"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"twitter", "consumer", "key"}) + `\b([a-zA-Z0-9]{25})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"twitter", "consumer", "secret"}) + `\b([a-zA-Z0-9]{50})\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{"twitter"}
-}
-
-// FromData will find and optionally verify Twitter secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- // find for consumer key + secrets
- keyMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatches[match[1]] = struct{}{}
- }
- secretMatches := make(map[string]struct{})
- for _, match := range secretPat.FindAllStringSubmatch(dataStr, -1) {
- secretMatches[match[1]] = struct{}{}
- }
-
- for key := range keyMatches {
- for secret := range secretMatches {
- key := strings.TrimSpace(key)
- secret := strings.TrimSpace(secret)
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_TwitterConsumerkey,
- Raw: []byte(key),
- RawV2: []byte(key + secret),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- bearerToken, err := fetchBearerToken(ctx, client, key, secret)
- if err == nil {
- isVerified, err := verifyBearerToken(ctx, client, bearerToken)
- s1.Verified = isVerified
- s1.SetVerificationError(err, key)
- } else {
- s1.SetVerificationError(err, key)
- }
- }
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_TwitterConsumerkey
-}
-
-func (s Scanner) Description() string {
- return "Twitter Consumer Keys and Secrets are used to authenticate API requests to Twitter. They allow access to Twitter's API to manage user data and perform actions on behalf of users."
-}
-
-func verifyBearerToken(ctx context.Context, client *http.Client, token string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.twitter.com/2/tweets/20", nil)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- switch res.StatusCode {
- case http.StatusOK, http.StatusForbidden:
- // 403 indicates lack of permission, but valid token (could be due to twitter free tier)
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
- }
-
- return false, err
-}
-
-func fetchBearerToken(ctx context.Context, client *http.Client, key, secret string) (string, error) {
- payload := strings.NewReader("grant_type=client_credentials")
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.twitter.com/oauth2/token", payload)
- if err != nil {
- return "", err
- }
- sEnc := b64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", key, secret)))
-
- req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc))
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
- res, err := client.Do(req)
- if err != nil {
- return "", err
- }
- defer res.Body.Close()
-
- switch res.StatusCode {
- case http.StatusOK:
- var token tokenResponse
- if err = json.NewDecoder(res.Body).Decode(&token); err != nil {
- return "", err
- }
- return token.AccessToken, nil
- default:
- return "", fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-type tokenResponse struct {
- TokenType string `json:"token_type"`
- AccessToken string `json:"access_token"`
-}
diff --git a/pkg/detectors/twitterconsumerkey/twitterconsumerkey_integration_test.go b/pkg/detectors/twitterconsumerkey/twitterconsumerkey_integration_test.go
deleted file mode 100644
index 8ba404b35062..000000000000
--- a/pkg/detectors/twitterconsumerkey/twitterconsumerkey_integration_test.go
+++ /dev/null
@@ -1,139 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package twitterconsumerkey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTwitterConsumerKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- key := testSecrets.MustGetField("TWITTER_CONSUMER_KEY")
- secret := testSecrets.MustGetField("TWITTER_CONSUMER_SECRET")
-
- inactiveKey := testSecrets.MustGetField("TWITTER_CONSUMER_KEY_INACTIVE")
- inactiveSecret := testSecrets.MustGetField("TWITTER_CONSUMER_SECRET_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitter key %s and secret %s within", key, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TwitterConsumerkey,
- Verified: true,
- Raw: []byte(key),
- RawV2: []byte(key + secret),
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a twitter key %s and secret %s within", inactiveKey, inactiveSecret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_TwitterConsumerkey,
- Verified: false,
- Raw: []byte(inactiveKey),
- RawV2: []byte(inactiveKey + inactiveSecret),
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the key & secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: 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("TwitterConsumerKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if len(got[i].RawV2) == 0 {
- t.Fatalf("no rawV2 secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("TwitterConsumerKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/twitterconsumerkey/twitterconsumerkey_test.go b/pkg/detectors/twitterconsumerkey/twitterconsumerkey_test.go
deleted file mode 100644
index 9d9bd49849b4..000000000000
--- a/pkg/detectors/twitterconsumerkey/twitterconsumerkey_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package twitterconsumerkey
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "xdIAmQrrNE8knt6fwO6BQ0MMA"
- invalidKey = "xdIAmQrrNE8k?t6fwO6BQ0MMA"
- validSecret = "6SIZsEihsjUTkGlUWpdPeLvmplgEYaI3FvKJ5o0cMijLlGaokd"
- invalidSecret = "6SIZsEihsjUTkG?UWpdPeLvmplgEYaI3FvKJ5o0cMijLlGaokd"
- keyword = "twitterconsumerkey"
-)
-
-func TestTwitterConsumerKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword twitterconsumerkey",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validSecret),
- want: []string{validKey + validSecret},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidSecret),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/tyntec/tyntec.go b/pkg/detectors/tyntec/tyntec.go
deleted file mode 100644
index e5fff0538675..000000000000
--- a/pkg/detectors/tyntec/tyntec.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package tyntec
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"tyntec"}) + `\b([a-zA-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"tyntec"}
-}
-
-// FromData will find and optionally verify Tyntec secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Tyntec,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.tyntec.com/2fa/v1/application", nil)
- if err != nil {
- continue
- }
- req.Header.Add("apiKey", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Tyntec
-}
-
-func (s Scanner) Description() string {
- return "Tyntec is a service providing communication APIs for messaging, voice, and phone number verification. Tyntec API keys can be used to access and manage these services."
-}
diff --git a/pkg/detectors/tyntec/tyntec_integration_test.go b/pkg/detectors/tyntec/tyntec_integration_test.go
deleted file mode 100644
index ddb10f9f8cc1..000000000000
--- a/pkg/detectors/tyntec/tyntec_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package tyntec
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTyntec_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TYNTEC")
- inactiveSecret := testSecrets.MustGetField("TYNTEC_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tyntec secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tyntec,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a tyntec secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Tyntec,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Tyntec.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Tyntec.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/tyntec/tyntec_test.go b/pkg/detectors/tyntec/tyntec_test.go
deleted file mode 100644
index 152358885bf2..000000000000
--- a/pkg/detectors/tyntec/tyntec_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tyntec
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "3AZo8XEgSEofwdIlSjdlvPfuzTpPIarb"
- invalidPattern = "3AZo8XEg?EofwdIlSjdlvPfuzTpPIarb"
- keyword = "tyntec"
-)
-
-func TestTyntec_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword tyntec",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/typeform/v1/typeform.go b/pkg/detectors/typeform/v1/typeform.go
deleted file mode 100644
index f8a2574b10d3..000000000000
--- a/pkg/detectors/typeform/v1/typeform.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package typeform
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"typeform"}) + `\b([0-9A-Za-z]{44})\b`)
-)
-
-func (s Scanner) Version() int { return 1 }
-
-// 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{"typeform"}
-}
-
-// FromData will find and optionally verify Typeform secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Typeform,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyTypeForm(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Typeform
-}
-
-func (s Scanner) Description() string {
- return "Typeform is a service for creating forms and surveys. Typeform API keys can be used to access and manage forms and responses."
-}
-
-func verifyTypeForm(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.typeform.com/me", nil)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/typeform/v1/typeform_integration_test.go b/pkg/detectors/typeform/v1/typeform_integration_test.go
deleted file mode 100644
index ac5971a37458..000000000000
--- a/pkg/detectors/typeform/v1/typeform_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package typeform
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTypeform_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TYPEFORM")
- inactiveSecret := testSecrets.MustGetField("TYPEFORM_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a typeform secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Typeform,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a typeform secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Typeform,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Typeform.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Typeform.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/typeform/v1/typeform_test.go b/pkg/detectors/typeform/v1/typeform_test.go
deleted file mode 100644
index c22c753ff032..000000000000
--- a/pkg/detectors/typeform/v1/typeform_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package typeform
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "tfpsOcxPYaEVD4SNQ5f7HptyomeOpE1SaEuEbkusbBLR"
- invalidPattern = "tf?sOcxPYaEVD4SNQ5f7HptyomeOpE1SaEuEbkusbBLR"
- keyword = "typeform"
-)
-
-func TestTypeform_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword typeform",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/typeform/v2/typeform.go b/pkg/detectors/typeform/v2/typeform.go
deleted file mode 100644
index a540b9eae7e2..000000000000
--- a/pkg/detectors/typeform/v2/typeform.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package typeform
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- keyPat = regexp.MustCompile(`\btfp_[a-zA-Z0-9_]{40,59}\b`)
-)
-
-func (s Scanner) getClient() *http.Client {
- if s.client != nil {
- return s.client
- }
-
- return client
-}
-
-func (s Scanner) Version() int { return 2 }
-
-// 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{"tfp_"}
-}
-
-type TypeFormResponse struct {
- UserID string `json:"user_id,omitempty"`
- Email string `json:"email,omitempty"`
- Alias string `json:"alias,omitempty"`
- Language string `json:"language,omitempty"`
-}
-
-// FromData will find and optionally verify Typeform secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllString(dataStr, -1)
-
- for _, match := range matches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Typeform,
- Raw: []byte(match),
- }
-
- if verify {
- verified, typeformResponse, requestErr := verifyMatch(ctx, s.getClient(), match)
- s1.Verified = verified
- s1.SetVerificationError(requestErr)
-
- if typeformResponse != nil {
- s1.ExtraData = map[string]string{
- "UserId": typeformResponse.UserID,
- "Email": typeformResponse.Email,
- "Alias": typeformResponse.Alias,
- "Language": typeformResponse.Language,
- }
- }
- }
- results = append(results, s1)
-
- }
-
- return results, nil
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, secret string) (bool, *TypeFormResponse, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.typeform.com/me", nil)
- if err != nil {
- return false, nil, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", secret))
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- if res.StatusCode == 200 {
- var response *TypeFormResponse
- if err = json.NewDecoder(res.Body).Decode(&response); err != nil {
- return false, nil, err
- }
-
- return true, response, nil
- } else if res.StatusCode == 401 || res.StatusCode == 403 {
- return false, nil, nil
- } else {
- return false, nil, fmt.Errorf("unexpected status code %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Typeform
-}
-
-func (s Scanner) Description() string {
- return "Typeform is a service for creating forms and surveys. Typeform API keys can be used to access and manage forms and responses."
-}
diff --git a/pkg/detectors/typeform/v2/typeform_integration_test.go b/pkg/detectors/typeform/v2/typeform_integration_test.go
deleted file mode 100644
index b3ca87fad837..000000000000
--- a/pkg/detectors/typeform/v2/typeform_integration_test.go
+++ /dev/null
@@ -1,133 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package typeform
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTypeform_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TYPEFORM_V2")
- inactiveSecret := testSecrets.MustGetField("TYPEFORM_V2_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a typeform secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Typeform,
- Verified: true,
- ExtraData: map[string]string{
- "Alias": "TruffleSecurity Detectors",
- "Email": "detectors@trufflesec.com",
- "Language": "en",
- "UserId": "01JEX5WZZGGEC89F5E4DKW4144",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a typeform secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Typeform,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("TypeForm.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("TypeForm.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/typeform/v2/typeform_test.go b/pkg/detectors/typeform/v2/typeform_test.go
deleted file mode 100644
index 7f61c8bbee70..000000000000
--- a/pkg/detectors/typeform/v2/typeform_test.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package typeform
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestTypeformV2_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern (v2)",
- input: "typeform_token = 'tfp_ABCDEfghijKLMNOPqrstuvWXYZ0123456789ABCDEFGH_ijK12340qqqBBB'",
- want: []string{"tfp_ABCDEfghijKLMNOPqrstuvWXYZ0123456789ABCDEFGH_ijK12340qqqBBB"},
- },
- {
- name: "finds all matches (v2)",
- input: `typeform_token1 = 'tfp_ABCDEfghijKLMNOPqrstuvWXYZ0123456789ABCDEFGH_ijK12340qqqBBB'
-typeform_token2 = 'tfp_943af478d3ff3d4d760020c11af102b79c440513'`,
- want: []string{"tfp_ABCDEfghijKLMNOPqrstuvWXYZ0123456789ABCDEFGH_ijK12340qqqBBB", "tfp_943af478d3ff3d4d760020c11af102b79c440513"},
- },
- {
- name: "invalid pattern",
- input: "typeform_token = 'tfp_1'",
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/typetalk/typetalk.go b/pkg/detectors/typetalk/typetalk.go
deleted file mode 100644
index 850a0f91acd0..000000000000
--- a/pkg/detectors/typetalk/typetalk.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package typetalk
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"typetalk"}) + `\b([0-9a-zA-Z]{64})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"typetalk"}) + `\b([0-9a-zA-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"typetalk"}
-}
-
-// FromData will find and optionally verify Typetalk secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idMatch := range idMatches {
-
- resIdMatch := strings.TrimSpace(idMatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Typetalk,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("https://typetalk.com/oauth2/access_token?client_id=%s&client_secret=%s&grant_type=client_credentials", resIdMatch, resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Typetalk
-}
-
-func (s Scanner) Description() string {
- return "Typetalk is a Japanese chat app. API keys can be used to read potentially sensitive chat messages."
-}
diff --git a/pkg/detectors/typetalk/typetalk_integration_test.go b/pkg/detectors/typetalk/typetalk_integration_test.go
deleted file mode 100644
index f99d6484c56f..000000000000
--- a/pkg/detectors/typetalk/typetalk_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package typetalk
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestTypetalk_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("TYPETALK")
- id := testSecrets.MustGetField("TYPETALK_ID")
- inactiveSecret := testSecrets.MustGetField("TYPETALK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a typetalk secret %s within typetalk %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Typetalk,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a typetalk secret %s within typetalk %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Typetalk,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Typetalk.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Typetalk.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/typetalk/typetalk_test.go b/pkg/detectors/typetalk/typetalk_test.go
deleted file mode 100644
index ec6b3baeeaf9..000000000000
--- a/pkg/detectors/typetalk/typetalk_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package typetalk
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "MHxch0NrViQ41psiK4ypAwrUxzbKC3YeEuL1CPRedPiVouXflbdW4nKcaJ8Iidua"
- invalidKey = "MHxch0NrViQ41psiK4ypAwrUxzbKC3Ye?uL1CPRedPiVouXflbdW4nKcaJ8Iidua"
- validId = "ScH0wbXrV7gBPgTZgjpNy2mtcHbelKGh"
- invalidId = "ScH0wbXrV7?BPgTZgjpNy2mtcHbelKGh"
- keyword = "typetalk"
-)
-
-func TestTypetalk_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword typetalk",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/ubidots/ubidots.go b/pkg/detectors/ubidots/ubidots.go
deleted file mode 100644
index 94e6a944df38..000000000000
--- a/pkg/detectors/ubidots/ubidots.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package ubidots
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(BBFF-[0-9a-zA-Z]{30})\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{"BBFF-"}
-}
-
-// FromData will find and optionally verify Ubidots secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Ubidots,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://industrial.api.ubidots.com/api/v1.6/variables/", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-Auth-Token", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Ubidots
-}
-
-func (s Scanner) Description() string {
- return "Ubidots is an IoT platform that provides tools to manage and analyze IoT devices. Ubidots tokens can be used to access and manipulate data from IoT devices."
-}
diff --git a/pkg/detectors/ubidots/ubidots_integration_test.go b/pkg/detectors/ubidots/ubidots_integration_test.go
deleted file mode 100644
index 4dd26f2fc9d2..000000000000
--- a/pkg/detectors/ubidots/ubidots_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package ubidots
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUbidots_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("UBIDOTS_TOKEN")
- inactiveSecret := testSecrets.MustGetField("UBIDOTS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ubidots secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Ubidots,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a ubidots secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Ubidots,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Ubidots.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Ubidots.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/ubidots/ubidots_test.go b/pkg/detectors/ubidots/ubidots_test.go
deleted file mode 100644
index 7b7f102057a0..000000000000
--- a/pkg/detectors/ubidots/ubidots_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package ubidots
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "BBFF-ZgyYQIXWnkYcnga3ShOXhm4DlMJpSE"
- invalidPattern = "BBFF-ZgyYQIXW?kYcnga3ShOXhm4DlMJpSE"
- keyword = "ubidots"
-)
-
-func TestUbidots_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword ubidots",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/uclassify/uclassify.go b/pkg/detectors/uclassify/uclassify.go
deleted file mode 100644
index d70bdd167428..000000000000
--- a/pkg/detectors/uclassify/uclassify.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package uclassify
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"uclassify"}) + `\b([a-z0-9A-Z]{12})\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{"uclassify"}
-}
-
-// FromData will find and optionally verify Uclassify secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Uclassify,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{"texts":["I am so happy today"]}`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.uclassify.com/v1/uClassify/Sentiment/classify", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Uclassify
-}
-
-func (s Scanner) Description() string {
- return "Uclassify is a text classification service. Uclassify API keys can be used to classify text into various categories such as sentiment, topic, etc."
-}
diff --git a/pkg/detectors/uclassify/uclassify_integration_test.go b/pkg/detectors/uclassify/uclassify_integration_test.go
deleted file mode 100644
index f3b4e2a4041d..000000000000
--- a/pkg/detectors/uclassify/uclassify_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package uclassify
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUclassify_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("UCLASSIFY")
- inactiveSecret := testSecrets.MustGetField("UCLASSIFY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uclassify secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Uclassify,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uclassify secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Uclassify,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Uclassify.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Uclassify.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/uclassify/uclassify_test.go b/pkg/detectors/uclassify/uclassify_test.go
deleted file mode 100644
index e5ef3bc8732e..000000000000
--- a/pkg/detectors/uclassify/uclassify_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package uclassify
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "xM98lZh20vZs"
- invalidPattern = "xM98lZ?20vZs"
- keyword = "uclassify"
-)
-
-func TestUclassify_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword uclassify",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/unifyid/unifyid.go b/pkg/detectors/unifyid/unifyid.go
deleted file mode 100644
index 4a71c3088545..000000000000
--- a/pkg/detectors/unifyid/unifyid.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package unifyid
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"unify"}) + `\b([0-9A-Za-z_=-]{44})`)
-)
-
-// 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{"unify"}
-}
-
-// FromData will find and optionally verify Unifyid secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_UnifyID,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{"token": "sample"}`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.unify.id/v1/humandetect/verify", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("X-API-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
- if (res.StatusCode >= 200 && res.StatusCode < 300) || (res.StatusCode == 400 && strings.Contains(body, "invalid token")) {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_UnifyID
-}
-
-func (s Scanner) Description() string {
- return "UnifyID provides human detection services. These API keys can be used to verify human presence."
-}
diff --git a/pkg/detectors/unifyid/unifyid_integration_test.go b/pkg/detectors/unifyid/unifyid_integration_test.go
deleted file mode 100644
index 4e59fce1e1b8..000000000000
--- a/pkg/detectors/unifyid/unifyid_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package unifyid
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUnifyid_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("UNIFY")
- inactiveSecret := testSecrets.MustGetField("UNIFY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a unifyid secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_UnifyID,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a unifyid secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_UnifyID,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Unifyid.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Unifyid.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/unifyid/unifyid_test.go b/pkg/detectors/unifyid/unifyid_test.go
deleted file mode 100644
index 3d114f1217d1..000000000000
--- a/pkg/detectors/unifyid/unifyid_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package unifyid
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "AstGk-tlQZmhHAzRpyMO841oOydxYadtwiixD_IHuVEy"
- invalidPattern = "AstGk-tlQZmhHAzRpyMO84?oOydxYadtwiixD_IHuVEy"
- keyword = "unify"
-)
-
-func TestUnifyid_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword unify",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/unplugg/unplugg.go b/pkg/detectors/unplugg/unplugg.go
deleted file mode 100644
index 39c809efa269..000000000000
--- a/pkg/detectors/unplugg/unplugg.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package unplugg
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"unplu"}) + `\b([a-z0-9]{64})\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{"unplu"}
-}
-
-// FromData will find and optionally verify Unplugg secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Unplugg,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader(`{"data":[{"timestamp":1466640000,"value":0.27589941523037853},{"timestamp":1466640900,"value":0.4059699097648263}],"forecast_to":1458136800,"callback":"https://yourdomain.com/samplecallback"}`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.unplu.gg/forecast", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("x-access-token", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Unplugg
-}
-
-func (s Scanner) Description() string {
- return "Unplugg is a service for forecasting and data analysis. Unplugg API keys can be used to access and manipulate forecast data."
-}
diff --git a/pkg/detectors/unplugg/unplugg_integration_test.go b/pkg/detectors/unplugg/unplugg_integration_test.go
deleted file mode 100644
index f021bfa7c9de..000000000000
--- a/pkg/detectors/unplugg/unplugg_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package unplugg
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUnplugg_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("UNPLUGG")
- inactiveSecret := testSecrets.MustGetField("UNPLUGG_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a unplugg secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Unplugg,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a unplugg secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Unplugg,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Unplugg.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Unplugg.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/unplugg/unplugg_test.go b/pkg/detectors/unplugg/unplugg_test.go
deleted file mode 100644
index 3b5e7d2e729f..000000000000
--- a/pkg/detectors/unplugg/unplugg_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package unplugg
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "jk0kl5thu7l6ny1zzxjbml6762ipy9wc6zlcnxyakwnisxbj5m2svwaz0wxdbgco"
- invalidPattern = "jk0kl5thu7l?ny1zzxjbml6762ipy9wc6zlcnxyakwnisxbj5m2svwaz0wxdbgco"
- keyword = "unplugg"
-)
-
-func TestUnplugg_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword unplugg",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/unsplash/unsplash.go b/pkg/detectors/unsplash/unsplash.go
deleted file mode 100644
index c0e9d1826af9..000000000000
--- a/pkg/detectors/unsplash/unsplash.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package unsplash
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"unsplash"}) + `\b([0-9A-Za-z_]{43})\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{"unsplash"}
-}
-
-// FromData will find and optionally verify Unsplash secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Unsplash,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.unsplash.com/photos/?client_id="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Unsplash
-}
-
-func (s Scanner) Description() string {
- return "Unsplash is a website dedicated to sharing stock photography under the Unsplash license. Unsplash API keys can be used to access and modify Unsplash data."
-}
diff --git a/pkg/detectors/unsplash/unsplash_integration_test.go b/pkg/detectors/unsplash/unsplash_integration_test.go
deleted file mode 100644
index 20941b87e0a0..000000000000
--- a/pkg/detectors/unsplash/unsplash_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package unsplash
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUnsplash_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("UNSPLASH")
- inactiveSecret := testSecrets.MustGetField("UNSPLASH_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a unsplash secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Unsplash,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a unsplash secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Unsplash,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Unsplash.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Unsplash.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/unsplash/unsplash_test.go b/pkg/detectors/unsplash/unsplash_test.go
deleted file mode 100644
index eea0e86e0ab9..000000000000
--- a/pkg/detectors/unsplash/unsplash_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package unsplash
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "xpDbiR6oablsvuTJoG182EhKSesjV96AvReWRcKHZ3p"
- invalidPattern = "xpDbiR6oablsvuTJoG182?hKSesjV96AvReWRcKHZ3p"
- keyword = "unsplash"
-)
-
-func TestUnsplash_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword unsplash",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/upcdatabase/upcdatabase.go b/pkg/detectors/upcdatabase/upcdatabase.go
deleted file mode 100644
index 7774ca93befe..000000000000
--- a/pkg/detectors/upcdatabase/upcdatabase.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package upcdatabase
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"upcdatabase"}) + `\b([A-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"upcdatabase"}
-}
-
-// FromData will find and optionally verify UPCDatabase secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_UPCDatabase,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.upcdatabase.org/product/0111222333446?apikey=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err == nil {
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `added_time`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_UPCDatabase
-}
-
-func (s Scanner) Description() string {
- return "UPCDatabase is a service that provides access to a database of product information via API. The API key can be used to query product details."
-}
diff --git a/pkg/detectors/upcdatabase/upcdatabase_integration_test.go b/pkg/detectors/upcdatabase/upcdatabase_integration_test.go
deleted file mode 100644
index 1bf3e3a28f16..000000000000
--- a/pkg/detectors/upcdatabase/upcdatabase_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package upcdatabase
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUPCDatabase_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("UPCDATABASE")
- inactiveSecret := testSecrets.MustGetField("UPCDATABASE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a upcdatabase secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_UPCDatabase,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a upcdatabase secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_UPCDatabase,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("UPCDatabase.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("UPCDatabase.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/upcdatabase/upcdatabase_test.go b/pkg/detectors/upcdatabase/upcdatabase_test.go
deleted file mode 100644
index 73b3efe767d1..000000000000
--- a/pkg/detectors/upcdatabase/upcdatabase_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package upcdatabase
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "CMJC3HG2BC4MSDUVGGO4ZO4VWDRHB4SO"
- invalidPattern = "CMJC3H?2BC4MSDUVGGO4ZO4VWDRHB4SO"
- keyword = "upcdatabase"
-)
-
-func TestUPCDatabase_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword upcdatabase",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/uplead/uplead.go b/pkg/detectors/uplead/uplead.go
deleted file mode 100644
index 1a141fc8ec1a..000000000000
--- a/pkg/detectors/uplead/uplead.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package uplead
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"uplead"}) + `\b([a-z0-9-]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"uplead"}
-}
-
-// FromData will find and optionally verify Uplead secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Uplead,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.uplead.com/v2/credits", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Uplead
-}
-
-func (s Scanner) Description() string {
- return "Uplead is a B2B contact data provider. Uplead API keys can be used to access and manage contact data."
-}
diff --git a/pkg/detectors/uplead/uplead_integration_test.go b/pkg/detectors/uplead/uplead_integration_test.go
deleted file mode 100644
index 6268796550b7..000000000000
--- a/pkg/detectors/uplead/uplead_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package uplead
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUplead_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("UPLEAD")
- inactiveSecret := testSecrets.MustGetField("UPLEAD_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uplead secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Uplead,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uplead secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Uplead,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Uplead.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatal("no raw secret present")
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Uplead.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/uplead/uplead_test.go b/pkg/detectors/uplead/uplead_test.go
deleted file mode 100644
index 7d4b0bef47b8..000000000000
--- a/pkg/detectors/uplead/uplead_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package uplead
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "b1xnxn56l6jzvq3j9m2o0oio-aga9bzq"
- invalidPattern = "b1xnxn56l6jzvq3j?m2o0oio-aga9bzq"
- keyword = "uplead"
-)
-
-func TestUplead_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword uplead",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/uploadcare/uploadcare.go b/pkg/detectors/uploadcare/uploadcare.go
deleted file mode 100644
index df0c30f37758..000000000000
--- a/pkg/detectors/uploadcare/uploadcare.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package uploadcare
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"uploadcare"}) + `\b([a-z0-9]{20})\b`)
- publicKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"uploadcare"}) + `\b([a-z0-9]{20})\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{"uploadcare"}
-}
-
-// FromData will find and optionally verify UploadCare secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- publicMatches := publicKeyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, publicMatch := range publicMatches {
- publicKeyMatch := strings.TrimSpace(publicMatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_UploadCare,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.uploadcare.com/files/", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.uploadcare-v0.5+json")
- req.Header.Add("Authorization", fmt.Sprintf("Uploadcare.Simple %s:%s", publicKeyMatch, resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_UploadCare
-}
-
-func (s Scanner) Description() string {
- return "UploadCare is a service for handling file uploads and transformations. UploadCare keys can be used to manage and access files within the UploadCare system."
-}
diff --git a/pkg/detectors/uploadcare/uploadcare_integration_test.go b/pkg/detectors/uploadcare/uploadcare_integration_test.go
deleted file mode 100644
index 25e46a061216..000000000000
--- a/pkg/detectors/uploadcare/uploadcare_integration_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package uploadcare
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUploadCare_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("UPLOADCARE")
- publicKey := testSecrets.MustGetField("UPLOADCARE_PUBLIC")
- inactiveSecret := testSecrets.MustGetField("UPLOADCARE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uploadcare secret %s within uploadcare %s", secret, publicKey)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_UploadCare,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uploadcare secret %s within uploadcare %s but not valid", inactiveSecret, publicKey)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_UploadCare,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("UploadCare.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("UploadCare.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/uploadcare/uploadcare_test.go b/pkg/detectors/uploadcare/uploadcare_test.go
deleted file mode 100644
index 0ab91249016a..000000000000
--- a/pkg/detectors/uploadcare/uploadcare_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package uploadcare
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "ktoicf994nsq4ht02f1k"
- invalidKey = "ktoicf994n?q4ht02f1k"
- validPublicKey = "vq2fk1qxwn4bker4ukk1"
- invalidPublicKey = "vq2fk1qxwn?bker4ukk1"
- keyword = "uploadcare"
-)
-
-func TestUploadCare_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword uploadcare",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validPublicKey),
- want: []string{validKey, validPublicKey, validPublicKey, validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidPublicKey),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/uptimerobot/uptimerobot.go b/pkg/detectors/uptimerobot/uptimerobot.go
deleted file mode 100644
index d6aeb11136af..000000000000
--- a/pkg/detectors/uptimerobot/uptimerobot.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package uptimerobot
-
-import (
- "context"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"uptimerobot"}) + `\b([a-zA-Z0-9]{9}-[a-zA-Z0-9]{24})\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{"uptimerobot"}
-}
-
-// FromData will find and optionally verify UptimeRobot secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_UptimeRobot,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.uptimerobot.com/v2/getMonitors?api_key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"ok"`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_UptimeRobot
-}
-
-func (s Scanner) Description() string {
- return "UptimeRobot provides website monitoring services. The API keys can be used to manage and monitor these services."
-}
diff --git a/pkg/detectors/uptimerobot/uptimerobot_integration_test.go b/pkg/detectors/uptimerobot/uptimerobot_integration_test.go
deleted file mode 100644
index 7e6637138151..000000000000
--- a/pkg/detectors/uptimerobot/uptimerobot_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package uptimerobot
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUptimeRobot_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("UPTIMEROBOT")
- inactiveSecret := testSecrets.MustGetField("UPTIMEROBOT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uptimerobot secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_UptimeRobot,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uptimerobot secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_UptimeRobot,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("UptimeRobot.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("UptimeRobot.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/uptimerobot/uptimerobot_test.go b/pkg/detectors/uptimerobot/uptimerobot_test.go
deleted file mode 100644
index 1b1ae7226f88..000000000000
--- a/pkg/detectors/uptimerobot/uptimerobot_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package uptimerobot
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "S0CUf2rZs-SyEiUoQb9g2p6qdwcypE8jjY"
- invalidPattern = "S0CUf2rZs-SyEiUoQ?9g2p6qdwcypE8jjY"
- keyword = "uptimerobot"
-)
-
-func TestUptimeRobot_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword uptimerobot",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/upwave/upwave.go b/pkg/detectors/upwave/upwave.go
deleted file mode 100644
index c6f0b66d6476..000000000000
--- a/pkg/detectors/upwave/upwave.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package upwave
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"upwave"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"upwave"}
-}
-
-// FromData will find and optionally verify Upwave secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Upwave,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.upwave.io/workspaces/", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Upwave
-}
-
-func (s Scanner) Description() string {
- return "Upwave is a project management tool. Upwave API keys can be used to access and manage project data."
-}
diff --git a/pkg/detectors/upwave/upwave_integration_test.go b/pkg/detectors/upwave/upwave_integration_test.go
deleted file mode 100644
index 15371ebe1789..000000000000
--- a/pkg/detectors/upwave/upwave_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package upwave
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUpwave_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("UPWAVE")
- inactiveSecret := testSecrets.MustGetField("UPWAVE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a upwave secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Upwave,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a upwave secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Upwave,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Upwave.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Upwave.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/upwave/upwave_test.go b/pkg/detectors/upwave/upwave_test.go
deleted file mode 100644
index 6f6ed04a0824..000000000000
--- a/pkg/detectors/upwave/upwave_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package upwave
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "mgepiy2lx8tttu5h45efviri2ngz83y0"
- invalidPattern = "mgepiy2lx8tttu5h?5efviri2ngz83y0"
- keyword = "upwave"
-)
-
-func TestUpwave_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword upwave",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/uri/uri.go b/pkg/detectors/uri/uri.go
deleted file mode 100644
index a140c58a09e4..000000000000
--- a/pkg/detectors/uri/uri.go
+++ /dev/null
@@ -1,199 +0,0 @@
-package uri
-
-import (
- "context"
- "errors"
- "io"
- "net"
- "net/http"
- "net/url"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
- logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- allowKnownTestSites bool
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ interface {
- detectors.Detector
- detectors.CustomFalsePositiveChecker
-} = (*Scanner)(nil)
-
-var (
- keyPat = regexp.MustCompile(`\bhttps?:\/\/[\w!#$%&()*+,\-./;<=>?@[\\\]^_{|}~]{0,50}:([\w!#$%&()*+,\-./:;<=>?[\\\]^_{|}~]{3,50})@[a-zA-Z0-9.-]+(?:\.[a-zA-Z]{2,})?(?::\d{1,5})?[\w/]+\b`)
-
- // TODO: make local addr opt-out
- defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
-
- hostNotFoundCache = simple.NewCache[struct{}]()
-)
-
-// 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{"http://", "https://"}
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_URI
-}
-
-func (s Scanner) Description() string {
- return "This detector identifies URLs with embedded credentials, which can be used to access web resources without explicit user interaction."
-}
-
-// FromData will find and optionally verify URI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- logger := logContext.AddLogger(ctx).Logger().WithName("uri")
- dataStr := string(data)
-
- uriMatches := make(map[string]string)
- for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uriMatch := matches[0]
- if !s.allowKnownTestSites {
- if strings.Contains(uriMatch, "httpbin.org") {
- continue
- }
- if strings.Contains(uriMatch, "httpwatch.com") {
- continue
- }
- }
-
- password := matches[1]
- // Skip findings where the password only has "*" characters, this is a redacted password
- // Also include the url encoded "*" characters: "%2A"
- if strings.Trim(password, "*") == "" || strings.Trim(password, "%2A") == "" {
- continue
- }
-
- uriMatches[uriMatch] = password
- }
-
- for uri, password := range uriMatches {
- parsedURL, err := url.Parse(uri)
- if err != nil {
- // URL is invalid.
- continue
- }
- // URL does not contain a password.
- if _, ok := parsedURL.User.Password(); !ok {
- continue
- }
-
- // Safe, I think? (https://github.com/golang/go/issues/38351)
- rawUrl := *parsedURL
- rawUrlWithPath := rawUrl.String()
- // Removing the path causes possible deduplication issues if some paths have basic auth and some do not.
- rawUrl.Path = ""
- rawUrlWithoutPath := rawUrl.String()
-
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_URI,
- Raw: []byte(rawUrlWithoutPath),
- RawV2: []byte(rawUrlWithPath),
- Redacted: detectors.RedactURL(*parsedURL),
- }
-
- if verify {
- hostname := parsedURL.Hostname()
- if hostNotFoundCache.Exists(hostname) {
- logger.V(3).Info("Skipping uri: no such host", "host", hostname)
- continue
- }
-
- if s.client == nil {
- s.client = defaultClient
- }
- isVerified, vErr := verifyURL(ctx, s.client, parsedURL)
- r.Verified = isVerified
- if vErr != nil {
- var dnsErr *net.DNSError
- if errors.As(vErr, &dnsErr) && dnsErr.IsNotFound {
- hostNotFoundCache.Set(hostname, struct{}{})
- }
- r.SetVerificationError(vErr, password)
- }
- }
-
- if !r.Verified {
- // Skip unverified findings where the password starts with a `$` - it's almost certainly a variable.
- if strings.HasPrefix(password, "$") {
- continue
- }
- }
-
- results = append(results, r)
- }
-
- return results, nil
-}
-
-func (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {
- return false, ""
-}
-
-func verifyURL(ctx context.Context, client *http.Client, u *url.URL) (bool, error) {
- // defuse most SSRF payloads
- u.Path = strings.TrimSuffix(u.Path, "/")
- u.RawQuery = ""
- u.Fragment = ""
-
- credentialedURL := u.String()
-
- u.User = nil
- nonCredentialedURL := u.String()
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, credentialedURL, nil)
- if err != nil {
- return false, err
- }
-
- credentialedRes, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, credentialedRes.Body)
- _ = credentialedRes.Body.Close()
- }()
-
- // If the credentialed URL returns a non 2XX code, we can assume it's a false positive.
- if credentialedRes.StatusCode < 200 || credentialedRes.StatusCode > 299 {
- return false, nil
- }
-
- time.Sleep(time.Millisecond * 10)
-
- req, err = http.NewRequestWithContext(ctx, http.MethodGet, nonCredentialedURL, nil)
- if err != nil {
- return false, err
- }
-
- nonCredentialedRes, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, nonCredentialedRes.Body)
- _ = nonCredentialedRes.Body.Close()
- }()
-
- // If the non-credentialed URL returns a non 400-428 code and basic auth header, we can assume it's verified now.
- if nonCredentialedRes.StatusCode >= 400 && nonCredentialedRes.StatusCode < 429 {
- if nonCredentialedRes.Header.Get("WWW-Authenticate") != "" {
- return true, nil
- }
- }
-
- return false, nil
-}
diff --git a/pkg/detectors/uri/uri_integration_test.go b/pkg/detectors/uri/uri_integration_test.go
deleted file mode 100644
index 567c97979b5d..000000000000
--- a/pkg/detectors/uri/uri_integration_test.go
+++ /dev/null
@@ -1,165 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package uri
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestURI_FromChunk(t *testing.T) {
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, unverified, wrong username",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uri secret %s within", "https://user:pass@httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx")),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_URI,
- Verified: false,
- Redacted: "https://user:********@httpwatch.com",
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uri secret %s within", "https://httpwatch:pass@www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx")),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_URI,
- Verified: true,
- Redacted: "https://httpwatch:********@www.httpwatch.com",
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified, defused",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uri secret %s within", "https://httpwatch:pass@www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx?foo=bar")),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_URI,
- Verified: true,
- Redacted: "https://httpwatch:********@www.httpwatch.com",
- },
- },
- wantErr: false,
- },
- {
- name: "found, verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a uri secret %s within", "https://httpwatch:pass@www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx")),
- verify: true,
- },
- want: func() []detectors.Result {
- r := detectors.Result{
- DetectorType: detectorspb.DetectorType_URI,
- Verified: false,
- Redacted: "https://httpwatch:********@www.httpwatch.com",
- }
- r.SetVerificationError(fmt.Errorf("context deadline exceeded"))
- return []detectors.Result{r}
- }(),
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "bad scheme",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("file://user:pass@foo.com:123/wh/at/ever"),
- verify: true,
- },
- wantErr: false,
- },
- {
- name: "nothing found, password was redacted two different ways",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("Both %s and %s have been redacted within", "https://httpwatch::********@www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx?foo=bar", "https://httpwatch::%2A%2A%2A%2A%2A%2A@www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx?foo=bar")),
- verify: true,
- },
- want: []detectors.Result{},
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- tt.s.allowKnownTestSites = true
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("URI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- // if os.Getenv("FORCE_PASS_DIFF") == "true" {
- // return
- // }
- for i := range got {
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Errorf("URI.FromData() error = %v, wantVerificationErr %v", got[i].VerificationError(), tt.want[i])
- return
- }
- got[i].Raw = nil
- got[i].RawV2 = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("URI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/uri/uri_test.go b/pkg/detectors/uri/uri_test.go
deleted file mode 100644
index bdde2b9e8555..000000000000
--- a/pkg/detectors/uri/uri_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package uri
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "https://kaNydBSAodo87dsm9asuiSAFtsd7.com:1234@qYY3SylY7fHP"
- validPattern2 = `http://username:password@127.0.0.1
`
- invalidPattern = "https://kaNydBSAodo87dsm9asuiSAFtsd7.com.1234@qYY3SylY7fHP"
- keyword = "uri"
-)
-
-func TestURI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword uri",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - do not process duplicate",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern2),
- want: []string{"http://username:password@127.0.0.1"},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/urlscan/urlscan.go b/pkg/detectors/urlscan/urlscan.go
deleted file mode 100644
index 0f66546ffd12..000000000000
--- a/pkg/detectors/urlscan/urlscan.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package urlscan
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"urlscan"}) + `\b([a-z0-9-]{36})\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{"urlscan"}
-}
-
-// FromData will find and optionally verify Urlscan secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Urlscan,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://urlscan.io/user/quotas", nil)
- if err != nil {
- continue
- }
- req.Header.Add("API-Key", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Urlscan
-}
-
-func (s Scanner) Description() string {
- return "Urlscan is a service for scanning and analyzing websites. Urlscan API keys can be used to interact with the Urlscan service programmatically."
-}
diff --git a/pkg/detectors/urlscan/urlscan_integration_test.go b/pkg/detectors/urlscan/urlscan_integration_test.go
deleted file mode 100644
index 7008388c7fd1..000000000000
--- a/pkg/detectors/urlscan/urlscan_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package urlscan
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUrlscan_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("URLSCAN")
- inactiveSecret := testSecrets.MustGetField("URLSCAN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a urlscan secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Urlscan,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a urlscan secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Urlscan,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Urlscan.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Urlscan.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/urlscan/urlscan_test.go b/pkg/detectors/urlscan/urlscan_test.go
deleted file mode 100644
index d5d22eec7924..000000000000
--- a/pkg/detectors/urlscan/urlscan_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package urlscan
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "0l8zbe7ucnhdermmblvndjb2p22abcs7y7j8"
- invalidPattern = "0l8zbe7ucnhdermmbl?ndjb2p22abcs7y7j8"
- keyword = "urlscan"
-)
-
-func TestUrlscan_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword urlscan",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/user/user.go b/pkg/detectors/user/user.go
deleted file mode 100644
index aea72d3e0c7b..000000000000
--- a/pkg/detectors/user/user.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package user
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"user"}) + `\b([a-zA-Z0-9-._+=]{64})\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{"user"}
-}
-
-// FromData will find and optionally verify User secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_User,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://secretscanner.user.com/api/public/users/", nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_User
-}
-
-func (s Scanner) Description() string {
- return "User credentials can be used to authenticate and authorize actions within the User service, potentially allowing access to sensitive data and operations."
-}
diff --git a/pkg/detectors/user/user_integration_test.go b/pkg/detectors/user/user_integration_test.go
deleted file mode 100644
index 970e0877e235..000000000000
--- a/pkg/detectors/user/user_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package user
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUser_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("USER")
- inactiveSecret := testSecrets.MustGetField("USER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a user secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_User,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a user secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_User,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("User.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("User.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/user/user_test.go b/pkg/detectors/user/user_test.go
deleted file mode 100644
index 1847ee565f75..000000000000
--- a/pkg/detectors/user/user_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package user
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "OmFxWjhZvCpOeMsgTJdZMas+dlUpr=fa7+.QOKKYvi7RKWyBeHtaLa7_rzMhLrRd"
- invalidPattern = "OmFxWjhZvCpOeMsgTJdZMas+dlUpr=fa?+.QOKKYvi7RKWyBeHtaLa7_rzMhLrRd"
- keyword = "user"
-)
-
-func TestUser_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword user",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/userflow/userflow.go b/pkg/detectors/userflow/userflow.go
deleted file mode 100644
index 7cf64e3de8c4..000000000000
--- a/pkg/detectors/userflow/userflow.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package userflow
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"userflow"}) + `\b([0-9a-z_]{29})\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{"userflow"}
-}
-
-// FromData will find and optionally verify Userflow secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Userflow,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.userflow.com/users", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.userflow+json; version=3")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Userflow
-}
-
-func (s Scanner) Description() string {
- return "Userflow is a service for creating user onboarding experiences. Userflow API keys can be used to access and modify user onboarding data and workflows."
-}
diff --git a/pkg/detectors/userflow/userflow_integration_test.go b/pkg/detectors/userflow/userflow_integration_test.go
deleted file mode 100644
index 6321e454bf58..000000000000
--- a/pkg/detectors/userflow/userflow_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package userflow
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUserflow_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("USERFLOW")
- inactiveSecret := testSecrets.MustGetField("USERFLOW_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a userflow secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Userflow,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a userflow secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Userflow,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Userflow.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Userflow.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/userflow/userflow_test.go b/pkg/detectors/userflow/userflow_test.go
deleted file mode 100644
index 4764804e3ee0..000000000000
--- a/pkg/detectors/userflow/userflow_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package userflow
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "503ulfqkdfay0penkz0zkq1glayt6"
- invalidPattern = "503ulfqkdfay0p?nkz0zkq1glayt6"
- keyword = "userflow"
-)
-
-func TestUserflow_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword userflow",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/userstack/userstack.go b/pkg/detectors/userstack/userstack.go
deleted file mode 100644
index 864445c01a71..000000000000
--- a/pkg/detectors/userstack/userstack.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package userstack
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"userstack"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"userstack"}
-}
-
-// FromData will find and optionally verify UserStack secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_UserStack,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.userstack.com/detect?access_key=%s&ua=Mozilla/5.0", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err == nil {
- bodyString := string(bodyBytes)
- valid := strings.Contains(bodyString, `is_mobile_device`) || strings.Contains(bodyString, `"info":"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption."`)
- defer res.Body.Close()
- if valid {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_UserStack
-}
-
-func (s Scanner) Description() string {
- return "UserStack is a service that provides detailed information about the technology stack used by a website. UserStack API keys can be used to access this information."
-}
diff --git a/pkg/detectors/userstack/userstack_integration_test.go b/pkg/detectors/userstack/userstack_integration_test.go
deleted file mode 100644
index d436c0ddf949..000000000000
--- a/pkg/detectors/userstack/userstack_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package userstack
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestUserStack_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("USERSTACK")
- inactiveSecret := testSecrets.MustGetField("USERSTACK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a userstack secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_UserStack,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a userstack secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_UserStack,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("UserStack.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("UserStack.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/userstack/userstack_test.go b/pkg/detectors/userstack/userstack_test.go
deleted file mode 100644
index 80b6383b5f60..000000000000
--- a/pkg/detectors/userstack/userstack_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package userstack
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "fwtfmlat0nmosek0lgvl9o8y0o1xvazt"
- invalidPattern = "fwtfm?at0nmosek0lgvl9o8y0o1xvazt"
- keyword = "userstack"
-)
-
-func TestUserStack_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword userstack",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken.go b/pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken.go
deleted file mode 100644
index 37bd5107b4e9..000000000000
--- a/pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package vagrantcloudpersonaltoken
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"vagrant"}) + `\b([A-Za-z0-9]{14}.atlasv1.[A-Za-z0-9]{67})\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{"vagrant"}
-}
-
-// FromData will find and optionally verify Vagrantcloudpersonaltoken secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_VagrantCloudPersonalToken,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- req, err := http.NewRequestWithContext(ctx, "GET", "https://app.vagrantup.com/api/v2/authenticate", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %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 if res.StatusCode == 401 {
- // The secret is determinately not verified (nothing to do)
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_VagrantCloudPersonalToken
-}
-
-func (s Scanner) Description() string {
- return "Vagrant Cloud is a service for managing and distributing development environments. Personal tokens can be used to authenticate and interact with the Vagrant Cloud API."
-}
diff --git a/pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken_integration_test.go b/pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken_integration_test.go
deleted file mode 100644
index 6ecbafaebea4..000000000000
--- a/pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken_integration_test.go
+++ /dev/null
@@ -1,128 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package vagrantcloudpersonaltoken
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVagrantcloudpersonaltoken_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VAGRANTCLOUDPERSONALTOKEN")
- inactiveSecret := testSecrets.MustGetField("VAGRANTCLOUDPERSONALTOKEN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vagrantcloudpersonaltoken secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VagrantCloudPersonalToken,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vagrantcloudpersonaltoken secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VagrantCloudPersonalToken,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Vagrantcloudpersonaltoken.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Vagrantcloudpersonaltoken.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken_test.go b/pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken_test.go
deleted file mode 100644
index 9136f1c3cb52..000000000000
--- a/pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package vagrantcloudpersonaltoken
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "3xPXvIQhdxEXQGwatlasv1x1zK093TwRXADtoR720b1kCixmaWWgo8Ivyir5FsMFVUoCQwWYtGXxLChctCi8CRoSk2"
- invalidPattern = "3xPXvIQhdxEXQGwatlasv1x1zK093TwRXADtoR720b1kC?xmaWWgo8Ivyir5FsMFVUoCQwWYtGXxLChctCi8CRoSk2"
- keyword = "vagrantcloudpersonaltoken"
-)
-
-func TestVagrantcloudpersonaltoken_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword vagrantcloudpersonaltoken",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/vatlayer/vatlayer.go b/pkg/detectors/vatlayer/vatlayer.go
deleted file mode 100644
index deca19f33e8a..000000000000
--- a/pkg/detectors/vatlayer/vatlayer.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package vatlayer
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"vatlayer"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"vatlayer"}
-}
-
-// FromData will find and optionally verify VatLayer secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_VatLayer,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://www.apilayer.net/api/validate?access_key=%s&vat_number=LU26375245&format=1", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err == nil {
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `vat_number`) || strings.Contains(bodyString, `"info":"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption."`)
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_VatLayer
-}
-
-func (s Scanner) Description() string {
- return "VatLayer is a service that provides VAT number validation and VAT rate data for businesses. VatLayer keys can be used to access this service and retrieve VAT-related information."
-}
diff --git a/pkg/detectors/vatlayer/vatlayer_integration_test.go b/pkg/detectors/vatlayer/vatlayer_integration_test.go
deleted file mode 100644
index 57ac61124fe3..000000000000
--- a/pkg/detectors/vatlayer/vatlayer_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package vatlayer
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVatLayer_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VATLAYER")
- inactiveSecret := testSecrets.MustGetField("VATLAYER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vatlayer secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VatLayer,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vatlayer secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VatLayer,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("VatLayer.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("VatLayer.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/vatlayer/vatlayer_test.go b/pkg/detectors/vatlayer/vatlayer_test.go
deleted file mode 100644
index 272a355d100e..000000000000
--- a/pkg/detectors/vatlayer/vatlayer_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package vatlayer
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "uj05sglnibecnutz4ky665kpjcqgyara"
- invalidPattern = "uj05sglnibecnutz?ky665kpjcqgyara"
- keyword = "vatlayer"
-)
-
-func TestVatLayer_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword vatlayer",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/vbout/vbout.go b/pkg/detectors/vbout/vbout.go
deleted file mode 100644
index d5dfbb15604e..000000000000
--- a/pkg/detectors/vbout/vbout.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package vbout
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"vbout"}) + `\b([0-9]{25})\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{"vbout"}
-}
-
-// FromData will find and optionally verify Vbout secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Vbout,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.vbout.com/1/app/me.json?key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Vbout
-}
-
-func (s Scanner) Description() string {
- return "Vbout is a marketing automation platform. Vbout API keys can be used to access and manage marketing data and campaigns."
-}
diff --git a/pkg/detectors/vbout/vbout_integration_test.go b/pkg/detectors/vbout/vbout_integration_test.go
deleted file mode 100644
index c8eaa6575b46..000000000000
--- a/pkg/detectors/vbout/vbout_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package vbout
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVbout_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VBOUT")
- inactiveSecret := testSecrets.MustGetField("VBOUT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vbout secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Vbout,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vbout secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Vbout,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Vbout.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Vbout.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/vbout/vbout_test.go b/pkg/detectors/vbout/vbout_test.go
deleted file mode 100644
index 2382101d3fb1..000000000000
--- a/pkg/detectors/vbout/vbout_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package vbout
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "9734741337193905126710251"
- invalidPattern = "97?4741337193905126710251"
- keyword = "vbout"
-)
-
-func TestVbout_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword vbout",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/vercel/vercel.go b/pkg/detectors/vercel/vercel.go
deleted file mode 100644
index f2eb960fa0d7..000000000000
--- a/pkg/detectors/vercel/vercel.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package vercel
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"vercel"}) + `\b([a-zA-Z0-9]{24})\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{"vercel"}
-}
-
-// FromData will find and optionally verify Vercel secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Vercel,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.vercel.com/www/user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Vercel
-}
-
-func (s Scanner) Description() string {
- return "Vercel is a platform for frontend frameworks and static sites, built to integrate with your headless content, commerce, or database. Vercel API keys can be used to access and manage your Vercel projects and deployments."
-}
diff --git a/pkg/detectors/vercel/vercel_integration_test.go b/pkg/detectors/vercel/vercel_integration_test.go
deleted file mode 100644
index d9726d83fecf..000000000000
--- a/pkg/detectors/vercel/vercel_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package vercel
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVercel_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VERCEL_TOKEN")
- inactiveSecret := testSecrets.MustGetField("VERCEL_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vercel secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Vercel,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vercel secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Vercel,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Vercel.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Vercel.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/vercel/vercel_test.go b/pkg/detectors/vercel/vercel_test.go
deleted file mode 100644
index 47d7797b404d..000000000000
--- a/pkg/detectors/vercel/vercel_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package vercel
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "gGP4GJRR3TOpU7wzTY9UiSTQ"
- invalidPattern = "gGP4GJRR3TOp?7wzTY9UiSTQ"
- keyword = "vercel"
-)
-
-func TestVercel_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword vercel",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/verifier/verifier.go b/pkg/detectors/verifier/verifier.go
deleted file mode 100644
index ac8dbccad329..000000000000
--- a/pkg/detectors/verifier/verifier.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package verifier
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"verifier"}) + `\b([a-z0-9]{96})\b`)
- emailPat = regexp.MustCompile(detectors.PrefixRegex([]string{"verifier"}) + common.EmailPattern)
-)
-
-// 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{"verifier"}
-}
-
-// FromData will find and optionally verify Verifier secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- uniqueEmailMatches := make(map[string]struct{})
- for _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for emailMatch := range uniqueEmailMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Verifier,
- Raw: []byte(resMatch),
- }
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://verifier.meetchopra.com/verify/%s?token=%s", emailMatch, resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Verifier
-}
-
-func (s Scanner) Description() string {
- return "Verifier is a service used to verify the authenticity of a credential. The tokens can be used to validate user identities."
-}
diff --git a/pkg/detectors/verifier/verifier_integration_test.go b/pkg/detectors/verifier/verifier_integration_test.go
deleted file mode 100644
index d6e55a1861e3..000000000000
--- a/pkg/detectors/verifier/verifier_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package verifier
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVerifier_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VERIFIER")
- user := testSecrets.MustGetField("ACCOUNT_USER")
- inactiveSecret := testSecrets.MustGetField("VERIFIER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a verifier secret %s within verifier %s", secret, user)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Verifier,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a verifier secret %s within verifier %s but not valid", inactiveSecret, user)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Verifier,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Verifier.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Verifier.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/verifier/verifier_test.go b/pkg/detectors/verifier/verifier_test.go
deleted file mode 100644
index 22a78b4852d3..000000000000
--- a/pkg/detectors/verifier/verifier_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package verifier
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- verifier_key = abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
- verifier_email = verifiertest@example.com
- `
- invalidPattern = "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
-)
-
-func TestVerifier_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{"abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("verifier: %s", invalidPattern),
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 && test.want != nil {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- t.Errorf("expected %d results, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/verimail/verimail.go b/pkg/detectors/verimail/verimail.go
deleted file mode 100644
index 45268496e2e4..000000000000
--- a/pkg/detectors/verimail/verimail.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package verimail
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"verimail"}) + `\b([A-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"verimail"}
-}
-
-// FromData will find and optionally verify Verimail secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Verimail,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.verimail.io/v3/verify?key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Verimail
-}
-
-func (s Scanner) Description() string {
- return "Verimail is an email verification service that helps ensure email addresses are valid and deliverable. Verimail keys can be used to access the Verimail API for verifying email addresses."
-}
diff --git a/pkg/detectors/verimail/verimail_integration_test.go b/pkg/detectors/verimail/verimail_integration_test.go
deleted file mode 100644
index 87165a4c00f4..000000000000
--- a/pkg/detectors/verimail/verimail_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package verimail
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVerimail_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VERIMAIL_TOKEN")
- inactiveSecret := testSecrets.MustGetField("VERIMAIL_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a verimail secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Verimail,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a verimail secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Verimail,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Verimail.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Verimail.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/verimail/verimail_test.go b/pkg/detectors/verimail/verimail_test.go
deleted file mode 100644
index ef019dde7192..000000000000
--- a/pkg/detectors/verimail/verimail_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package verimail
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "G0PW4TG34CP100EJD73BGE79Y2WK1CTJ"
- invalidPattern = "G0PW4TG34CP100EJ?73BGE79Y2WK1CTJ"
- keyword = "verimail"
-)
-
-func TestVerimail_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword verimail",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/veriphone/veriphone.go b/pkg/detectors/veriphone/veriphone.go
deleted file mode 100644
index 03070558212e..000000000000
--- a/pkg/detectors/veriphone/veriphone.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package veriphone
-
-import (
- "context"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"veriphone"}) + `\b([0-9A-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"veriphone"}
-}
-
-// FromData will find and optionally verify Veriphone secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Veriphone,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.veriphone.io/v2/verify?phone=%252B49-15123577723&key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Veriphone
-}
-
-func (s Scanner) Description() string {
- return "Veriphone is a service that provides phone number validation. Veriphone API keys can be used to access the phone validation service."
-}
diff --git a/pkg/detectors/veriphone/veriphone_integration_test.go b/pkg/detectors/veriphone/veriphone_integration_test.go
deleted file mode 100644
index af712d5db67d..000000000000
--- a/pkg/detectors/veriphone/veriphone_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package veriphone
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVeriphone_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VERIPHONE")
- inactiveSecret := testSecrets.MustGetField("VERIPHONE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a veriphone secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Veriphone,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a veriphone secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Veriphone,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Veriphone.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Veriphone.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/veriphone/veriphone_test.go b/pkg/detectors/veriphone/veriphone_test.go
deleted file mode 100644
index fa62c8c7a5d5..000000000000
--- a/pkg/detectors/veriphone/veriphone_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package veriphone
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "T7OWX9ATDTVE1ITA1HON9MAASLBDWNZA"
- invalidPattern = "T7OWX9ATDTVE?ITA1HON9MAASLBDWNZA"
- keyword = "veriphone"
-)
-
-func TestVeriphone_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword veriphone",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/versioneye/versioneye.go b/pkg/detectors/versioneye/versioneye.go
deleted file mode 100644
index 642a689c4d2f..000000000000
--- a/pkg/detectors/versioneye/versioneye.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package versioneye
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"versioneye"}) + `\b([a-zA-Z0-9-]{40})\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{"versioneye"}
-}
-
-// FromData will find and optionally verify VersionEye secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_VersionEye,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.versioneye.com/api/v1/scans", nil)
- if err != nil {
- continue
- }
- req.Header.Add("apiKey", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_VersionEye
-}
-
-func (s Scanner) Description() string {
- return "VersionEye is a service that monitors your project dependencies and alerts you of any vulnerabilities. VersionEye API keys can be used to access and manage your project data."
-}
diff --git a/pkg/detectors/versioneye/versioneye_integration_test.go b/pkg/detectors/versioneye/versioneye_integration_test.go
deleted file mode 100644
index 336405d4206e..000000000000
--- a/pkg/detectors/versioneye/versioneye_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package versioneye
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVersionEye_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VERSIONEYE")
- inactiveSecret := testSecrets.MustGetField("VERSIONEYE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a versioneye secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VersionEye,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a versioneye secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VersionEye,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("VersionEye.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("VersionEye.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/versioneye/versioneye_test.go b/pkg/detectors/versioneye/versioneye_test.go
deleted file mode 100644
index 87ddd6345367..000000000000
--- a/pkg/detectors/versioneye/versioneye_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package versioneye
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "pSbrVZlhasYd50mEJEc2n7lm3UTut3ZOqBVk-uZe"
- invalidPattern = "pSbrVZlhasYd50mEJEc2?7lm3UTut3ZOqBVk-uZe"
- keyword = "versioneye"
-)
-
-func TestVersionEye_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword versioneye",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/viewneo/viewneo.go b/pkg/detectors/viewneo/viewneo.go
deleted file mode 100644
index 710a4d9b53f2..000000000000
--- a/pkg/detectors/viewneo/viewneo.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package viewneo
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"viewneo"}) + `\b([a-z0-9A-Z]{120,300}.[a-z0-9A-Z]{150,300}.[a-z0-9A-Z-_]{600,800})`)
-)
-
-// 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{"viewneo"}
-}
-
-const maxKeySize = 1500
-
-// MaxSecretSize returns the maximum size of a secret that this detector can find.
-func (s Scanner) MaxSecretSize() int64 { return maxKeySize }
-
-// FromData will find and optionally verify Viewneo secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Viewneo,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://cloud.viewneo.com/api/v1.0/playlist", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Viewneo
-}
-
-func (s Scanner) Description() string {
- return "Viewneo is a digital signage platform. Viewneo API keys can be used to access and manage digital signage content and settings."
-}
diff --git a/pkg/detectors/viewneo/viewneo_integration_test.go b/pkg/detectors/viewneo/viewneo_integration_test.go
deleted file mode 100644
index ff6c82645570..000000000000
--- a/pkg/detectors/viewneo/viewneo_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package viewneo
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestViewneo_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VIEWNEO")
- inactiveSecret := testSecrets.MustGetField("VIEWNEO_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a viewneo secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Viewneo,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a viewneo secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Viewneo,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Viewneo.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Viewneo.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/viewneo/viewneo_test.go b/pkg/detectors/viewneo/viewneo_test.go
deleted file mode 100644
index bc0aaacb96ed..000000000000
--- a/pkg/detectors/viewneo/viewneo_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package viewneo
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "cZZ32f1UDFCqsSP6uUlFyPLrXnGhgunvxc3jRsTO1mygukFtXxmk3sV0Q4PiGW8TstPPagjg8o3N0Jp25RKDJUOssch6rbSBVPc3R5vsRSa7y00CKKuu6T6N.2oV6hZlvOR7Jm3L6PzRLMbPRburA2FUfRlRLktcCbt2nBX1iyAfdMv8JvCjAhUJHT52PhiAT3ca7FNd5q5ZXAkn87LnuQhc5UHyuwD8gcWstOghUHZ20tcz7SjVuKyWZFgODlW2WczXqHKxaNhWYz4.839QXG7zlPYdYNhfbvQZe1zHr6bbbjIQYUs2q5whTtUWm8tCMWaOtm_DEZ_xKO5RUrajoClRedRiGK0fFAMshDSyAROOa7NXcE4WM_AuURDSif51QmcWY5HRITdd7y639Zc2Sz1kkUz-Ks_Aqe7xy0VUOlA8m4w2A7IfQ2iDtUeAlWIz1vsOihDxWeNqvTj5D5JOQcyRCiCfTfDWptrJCkKsMWMcDNRE773ypzQVn3r6VSVC63UqdT5Et5jpS5C1wFMuJDei5w7t4vPBTbodepVLtEkn4HcuyTEt0m-Rh_LIxMShlL56AeC7bVBNvvRpNMi_YT3wTozsXvAXEDS1bdOcD_MLk7-g8L1FfeBZxTnRfLR81idE4qR7ecTeNgfVvuiddb-IGrIAefADZ_Vzl49E3amY7twA7EqX04lBZiVfZsO1R0BlzsCLqQ10fsleLl-S00R01G1Fn2e2gkEkRkwOfxbA7BdTYJwz3s1m7rC2HmQLyT_-h8qE30fGzWkoq7INPSTmJ0EJOPDRY3TZi7axUSDEjZbF8TwXcD3jFDmaAYD3D4E5NSKnILnacXC-kfGZQcP4bcrPbHa4BoNN3kyt"
- invalidPattern = "c?Z32f1UDFCqsSP6uUlFyPLrXnGhgunvxc3jRsTO1mygukFtXxmk3sV0Q4PiGW8TstPPagjg8o3N0Jp25RKDJUOssch6rbSBVPc3R5vsRSa7y00CKKuu6T6N.2oV6hZlvOR7Jm3L6PzRLMbPRburA2FUfRlRLktcCbt2nBX1iyAfdMv8JvCjAhUJHT52PhiAT3ca7FNd5q5ZXAkn87LnuQhc5UHyuwD8gcWstOghUHZ20tcz7SjVuKyWZFgODlW2WczXqHKxaNhWYz4.839QXG7zlPYdYNhfbvQZe1zHr6bbbjIQYUs2q5whTtUWm8tCMWaOtm_DEZ_xKO5RUrajoClRedRiGK0fFAMshDSyAROOa7NXcE4WM_AuURDSif51QmcWY5HRITdd7y639Zc2Sz1kkUz-Ks_Aqe7xy0VUOlA8m4w2A7IfQ2iDtUeAlWIz1vsOihDxWeNqvTj5D5JOQcyRCiCfTfDWptrJCkKsMWMcDNRE773ypzQVn3r6VSVC63UqdT5Et5jpS5C1wFMuJDei5w7t4vPBTbodepVLtEkn4HcuyTEt0m-Rh_LIxMShlL56AeC7bVBNvvRpNMi_YT3wTozsXvAXEDS1bdOcD_MLk7-g8L1FfeBZxTnRfLR81idE4qR7ecTeNgfVvuiddb-IGrIAefADZ_Vzl49E3amY7twA7EqX04lBZiVfZsO1R0BlzsCLqQ10fsleLl-S00R01G1Fn2e2gkEkRkwOfxbA7BdTYJwz3s1m7rC2HmQLyT_-h8qE30fGzWkoq7INPSTmJ0EJOPDRY3TZi7axUSDEjZbF8TwXcD3jFDmaAYD3D4E5NSKnILnacXC-kfGZQcP4bcrPbHa4BoNN3kyt"
- keyword = "viewneo"
-)
-
-func TestViewneo_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword viewneo",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/virustotal/virustotal.go b/pkg/detectors/virustotal/virustotal.go
deleted file mode 100644
index 063dd2578799..000000000000
--- a/pkg/detectors/virustotal/virustotal.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package virustotal
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"virustotal"}) + `\b([a-f0-9]{64})\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{"virustotal"}
-}
-
-// FromData will find and optionally verify VirusTotal secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_VirusTotal,
- Raw: []byte(resMatch),
- }
-
- if verify {
- s1.Verified = verifyToken(ctx, client, resMatch)
- }
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func verifyToken(ctx context.Context, client *http.Client, token string) bool {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://www.virustotal.com/api/v3/metadata", nil)
- if err != nil {
- return false
- }
- req.Header.Add("x-apikey", token)
-
- res, err := client.Do(req)
- if err != nil {
- return false
- }
- defer res.Body.Close()
-
- if res.StatusCode < 200 || res.StatusCode >= 300 {
- return false
- }
- return true
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_VirusTotal
-}
-
-func (s Scanner) Description() string {
- return "VirusTotal is an online service that analyzes files and URLs for viruses, worms, trojans, and other kinds of malicious content. VirusTotal API keys can be used to access and integrate this service into applications."
-}
diff --git a/pkg/detectors/virustotal/virustotal_integration_test.go b/pkg/detectors/virustotal/virustotal_integration_test.go
deleted file mode 100644
index b416c118effd..000000000000
--- a/pkg/detectors/virustotal/virustotal_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package virustotal
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVirusTotal_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VIRUSTOTAL")
- inactiveSecret := testSecrets.MustGetField("VIRUSTOTAL_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a virustotal secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VirusTotal,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a virustotal secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VirusTotal,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("VirusTotal.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("VirusTotal.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/virustotal/virustotal_test.go b/pkg/detectors/virustotal/virustotal_test.go
deleted file mode 100644
index 199bd4897844..000000000000
--- a/pkg/detectors/virustotal/virustotal_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package virustotal
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "67d334aba3419b4d090ebeecac11b8830cafa388f9b6e3bfa97d03f4d25dbf46"
- invalidPattern = "67d334aba3419b4d?90ebeecac11b8830cafa388f9b6e3bfa97d03f4d25dbf46"
- keyword = "virustotal"
-)
-
-func TestVirusTotal_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword virustotal",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/visualcrossing/visualcrossing.go b/pkg/detectors/visualcrossing/visualcrossing.go
deleted file mode 100644
index 48bfa46e6f57..000000000000
--- a/pkg/detectors/visualcrossing/visualcrossing.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package visualcrossing
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"visualcrossing"}) + `\b([0-9A-Z]{25})\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{"visualcrossing"}
-}
-
-// FromData will find and optionally verify Visualcrossing secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_VisualCrossing,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/LA?key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_VisualCrossing
-}
-
-func (s Scanner) Description() string {
- return "Visual Crossing provides weather data services. Visual Crossing API keys can be used to access weather data and services."
-}
diff --git a/pkg/detectors/visualcrossing/visualcrossing_integration_test.go b/pkg/detectors/visualcrossing/visualcrossing_integration_test.go
deleted file mode 100644
index 9c570ee36938..000000000000
--- a/pkg/detectors/visualcrossing/visualcrossing_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package visualcrossing
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVisualcrossing_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VISUALCROSSING")
- inactiveSecret := testSecrets.MustGetField("VISUALCROSSING_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a visualcrossing secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VisualCrossing,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a visualcrossing secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VisualCrossing,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Visualcrossing.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Visualcrossing.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/visualcrossing/visualcrossing_test.go b/pkg/detectors/visualcrossing/visualcrossing_test.go
deleted file mode 100644
index 98e0a90b7945..000000000000
--- a/pkg/detectors/visualcrossing/visualcrossing_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package visualcrossing
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "9D39V79EZ6V3RO0YFV3XUOGLJ"
- invalidPattern = "9D39V79EZ6V3?O0YFV3XUOGLJ"
- keyword = "visualcrossing"
-)
-
-func TestVisualcrossing_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword visualcrossing",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/voiceflow/voiceflow.go b/pkg/detectors/voiceflow/voiceflow.go
deleted file mode 100644
index 10d5c2af374b..000000000000
--- a/pkg/detectors/voiceflow/voiceflow.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package voiceflow
-
-import (
- "bytes"
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Reference: https://developer.voiceflow.com/reference/project#dialog-manager-api-keys
- //
- // TODO: This includes Workspace and Legacy Workspace API keys; I haven't validated whether these actually work.
- // https://github.com/voiceflow/general-runtime/blob/master/tests/runtime/lib/DataAPI/utils.unit.ts
- keyPat = regexp.MustCompile(`\b(VF\.(?:(?:DM|WS)\.)?[a-fA-F0-9]{24}\.[a-zA-Z0-9]{16})\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{"vf", "dm"}
-}
-
-// FromData will find and optionally verify Voiceflow secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Voiceflow,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- // Fetch the state for a random user.
- payload := []byte(`{"question": "why is the sky blue?"}`)
- req, err := http.NewRequestWithContext(ctx, "POST", "https://general-runtime.voiceflow.com/knowledge-base/query", bytes.NewBuffer(payload))
- if err != nil {
- continue
- }
- req.Header.Set("Accept", "application/json")
- req.Header.Set("Authorization", resMatch)
- req.Header.Set("Content-Type", "application/json")
-
- res, err := client.Do(req)
- if err == nil {
- if res.StatusCode == http.StatusOK {
- s1.Verified = true
- } else if res.StatusCode == http.StatusUnauthorized {
- // The secret is determinately not verified (nothing to do)
- } else {
- var buf bytes.Buffer
- var bodyString string
- _, err = io.Copy(&buf, res.Body)
- if err == nil {
- bodyString = buf.String()
- }
- verificationErr := fmt.Errorf("unexpected HTTP response [status=%d, body=%s]", res.StatusCode, bodyString)
- s1.SetVerificationError(verificationErr, resMatch)
- }
- _ = res.Body.Close()
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Voiceflow
-}
-
-func (s Scanner) Description() string {
- return "Voiceflow is an AI service designed to transact with customers. API keys may be used to access customer data."
-}
diff --git a/pkg/detectors/voiceflow/voiceflow_integration_test.go b/pkg/detectors/voiceflow/voiceflow_integration_test.go
deleted file mode 100644
index 8163f6ef9474..000000000000
--- a/pkg/detectors/voiceflow/voiceflow_integration_test.go
+++ /dev/null
@@ -1,162 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package voiceflow
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVoiceflow_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VOICEFLOW")
- inactiveSecret := testSecrets.MustGetField("VOICEFLOW_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a voiceflow secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Voiceflow,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a voiceflow secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Voiceflow,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a voiceflow secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Voiceflow,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a voiceflow secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Voiceflow,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Voiceflow.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Voiceflow.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/voiceflow/voiceflow_test.go b/pkg/detectors/voiceflow/voiceflow_test.go
deleted file mode 100644
index 522fcf3b40f7..000000000000
--- a/pkg/detectors/voiceflow/voiceflow_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-package voiceflow
-
-import (
- "context"
- "testing"
-)
-
-func TestVoiceflow_Pattern(t *testing.T) {
- tests := []struct {
- name string
- data string
- shouldMatch bool
- match string
- }{
- // True positives
- // https://github.com/funDAOmental/endlessquest/blob/5c008f7c6a7e58c45a88b72fef4b965c258d665c/Voiceflow/agent-api/index.js#L6
- {
- name: `valid_result1`,
- data: `// z0MG IT'S NOT A SECRET (but we'll delete it)
-const API_KEY = "VF.DM.6469b4e5909a470007b96250.k4ip0SMy84jWlCsF"; // it should look like this: VF.DM.XXXXXXX.XXXXXX... keep this a secret!`,
- shouldMatch: true,
- match: `VF.DM.6469b4e5909a470007b96250.k4ip0SMy84jWlCsF`,
- },
- // https://github.com/sherifButt/ll-site/blob/b98b268214324da42a84e996e4c03c242e122680/src/components/Chatbot.jsx#L14
- {
- name: `valid_result2`,
- data: ` const runtime = useRuntime({
- verify: { authorization: 'VF.DM.652da078cde70b0008e1c5df.zsIo23VTxNXKfb9f' },
- session: { userID: 'user_123' },
- });`,
- shouldMatch: true,
- match: `VF.DM.652da078cde70b0008e1c5df.zsIo23VTxNXKfb9f`,
- },
- // https://github.com/the-vv/Voiceflow-chatbot/blob/324db17693dd46387ea7a020e92c4e79b94306c6/src/app/chat/chat.component.ts#L27
- {
- name: `valid_result3`,
- data: ` this.http.delete('https://general-runtime.voiceflow.com/state/user/TEST_USER', {
- headers: {
- Authorization: "VF.DM.652ecc210267ec00078fc726.ZFPdEwvU0d1jiIMq"
- }
- }).subscribe(res => {
- this.loading = false;
- this.doPrompt('', { action: { type: 'launch' } });
- })`,
- shouldMatch: true,
- match: `VF.DM.652ecc210267ec00078fc726.ZFPdEwvU0d1jiIMq`,
- },
- // https://github.com/legionX7/Graduation-Project-API/blob/451431771d3fba1d8c634b8855274b414d7aed6d/mainAPI.py#L547
- {
- name: `valid_result4`,
- data: `
-API_KEY = 'VF.DM.646388eb1419c80007bbbaa4.XHOqETFO3cvTxlGl'
-VERSION_ID = '646bc'`,
- shouldMatch: true,
- match: `VF.DM.646388eb1419c80007bbbaa4.XHOqETFO3cvTxlGl`,
- },
- // https://github.com/voiceflow/general-runtime/blob/master/tests/runtime/lib/DataAPI/utils.unit.ts
- {
- name: `valid_result5`,
- data: ` it('extracts ID from a Dialog Manager API key', () => {
- // eslint-disable-next-line no-secrets/no-secrets
- const key = 'VF.DM.628d5d92faf688001bda7907.dmC8KKO1oX8JO5ai';
- const result = utils.extractAPIKeyID(key);
-
- expect(result).to.equal('628d5d92faf688001bda7907');
- });`,
- shouldMatch: true,
- match: `VF.DM.628d5d92faf688001bda7907.dmC8KKO1oX8JO5ai`,
- },
- {
- name: `valid_result6_legacy`,
- data: ` it('extracts ID from a Workspace API key', () => {
- // eslint-disable-next-line no-secrets/no-secrets
- const key = 'VF.WS.62bcb0cca5184300066f5ac7.egnKyyzZksiS5iGa';
- const result = utils.extractAPIKeyID(key);
-
- expect(result).to.equal('62bcb0cca5184300066f5ac7');
- });
-`,
- shouldMatch: true,
- match: `VF.WS.62bcb0cca5184300066f5ac7.egnKyyzZksiS5iGa`,
- },
- {
- name: `valid_result7_legacy`,
- data: ` it('extracts ID from a Legacy Workspace API key', () => {
- // eslint-disable-next-line no-secrets/no-secrets
- const key = 'VF.62bcb0cca5184300066f5ac7.dmC8KKO1oX8JO5az';
- const result = utils.extractAPIKeyID(key);
-
- expect(result).to.equal('62bcb0cca5184300066f5ac7');
- });`,
- shouldMatch: true,
- match: `VF.62bcb0cca5184300066f5ac7.dmC8KKO1oX8JO5az`,
- },
-
- // False positives
- // https://github.com/ImperialCollegeLondon/voiceflow-integration-whatsapp/blob/0f3d6a5638b9acb4989d5bf8e77081cc78e9b976/README.md?plain=1#L155
- {
- name: `invalid_result1`,
- data: "Now, paste it in your .env file for the **VF_PROJECT_API** variable \n```VF_PROJECT_API='VF.DM.62xxxxxxxxxxxxxxxxxxxxxxx'```",
- shouldMatch: false,
- },
- // https://github.com/voiceflow/api-examples/blob/c3d8ba9ee8eced7ec8d241973b1eb0284aaec212/rust/src/main.rs#L5
- {
- name: `invalid_result2`,
- data: `const API_KEY: &str = "YOUR_API_KEY_HERE"; // it should look like this: VF.DM.XXXXXXX.XXXXXX... keep this a secret!`,
- 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("Voiceflow.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
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/voicegain/voicegain.go b/pkg/detectors/voicegain/voicegain.go
deleted file mode 100644
index 6c3eff23353a..000000000000
--- a/pkg/detectors/voicegain/voicegain.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package voicegain
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"voicegain"}) + `\b(ey[0-9a-zA-Z_-]{34}.ey[0-9a-zA-Z_-]{108}.[0-9a-zA-Z_-]{43})\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{"voicegain"}
-}
-
-// FromData will find and optionally verify Voicegain secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Voicegain,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.voicegain.ai/v1/sa/config", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Voicegain
-}
-
-func (s Scanner) Description() string {
- return "Voicegain is a speech recognition and natural language processing service. Voicegain API keys can be used to access and manage Voicegain services and data."
-}
diff --git a/pkg/detectors/voicegain/voicegain_integration_test.go b/pkg/detectors/voicegain/voicegain_integration_test.go
deleted file mode 100644
index 4a3ce826faa5..000000000000
--- a/pkg/detectors/voicegain/voicegain_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package voicegain
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVoicegain_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VOICEGAIN")
- inactiveSecret := testSecrets.MustGetField("VOICEGAIN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a voicegain secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Voicegain,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a voicegain secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Voicegain,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Voicegain.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Voicegain.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/voicegain/voicegain_test.go b/pkg/detectors/voicegain/voicegain_test.go
deleted file mode 100644
index ca914bc77c5e..000000000000
--- a/pkg/detectors/voicegain/voicegain_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package voicegain
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "ey7nVWxLrVTPaolyqAYxmedMyueqpSQ6eIfX.ey582H2Ghcpty-_E0Wn4syTEdwAdXRF6eYyKgHveC26fBGQ4BiZIS30BcPrr1JG2xiFV4-bEOzVAMcWEQkh-THo0Z3RT6Os45ZGD28dlaiicZH.i4G5a88NkP82UpJoh3vW-sHOBx-uX1tYwyGcxykJO55"
- invalidPattern = "ay7nVWxLrVTPaolyqAYxmedMyueqpSQ6eIfX.ey582H2Ghcpty-_E0Wn4syTEdwAdXRF6eYyKgHveC26fBGQ4BiZIS30BcPrr1JG2xiFV4-bEOzVAMcWEQkh-THo0Z3RT6Os45ZGD28dlaiicZH.i4G5a88NkP82UpJoh3vW-sHOBx-uX1tYwyGcxykJO55"
- keyword = "voicegain"
-)
-
-func TestVoicegain_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword voicegain",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/voodoosms/voodoosms.go b/pkg/detectors/voodoosms/voodoosms.go
deleted file mode 100644
index 7e2b8e427bfe..000000000000
--- a/pkg/detectors/voodoosms/voodoosms.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package voodoosms
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"voodoosms"}) + `\b([0-9a-zA-Z]{46})\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{"voodoosms"}
-}
-
-// FromData will find and optionally verify VoodooSMS secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_VoodooSMS,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.voodoosms.com/credits", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type'", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_VoodooSMS
-}
-
-func (s Scanner) Description() string {
- return "VoodooSMS is a service for sending SMS messages. VoodooSMS API keys can be used to send SMS messages and check account balance."
-}
diff --git a/pkg/detectors/voodoosms/voodoosms_integration_test.go b/pkg/detectors/voodoosms/voodoosms_integration_test.go
deleted file mode 100644
index 3728dde96026..000000000000
--- a/pkg/detectors/voodoosms/voodoosms_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package voodoosms
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVoodooSMS_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VOODOOSMS")
- inactiveSecret := testSecrets.MustGetField("VOODOOSMS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a voodoosms secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VoodooSMS,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a voodoosms secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VoodooSMS,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("VoodooSMS.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("VoodooSMS.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/voodoosms/voodoosms_test.go b/pkg/detectors/voodoosms/voodoosms_test.go
deleted file mode 100644
index 5711c0de6127..000000000000
--- a/pkg/detectors/voodoosms/voodoosms_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package voodoosms
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "qpbVNj3Fdxc38gIixFqXAndxfSvoafOWztBMIzsl0Y262i"
- invalidPattern = "qpbVNj3Fd?c38gIixFqXAndxfSvoafOWztBMIzsl0Y262i"
- keyword = "voodoosms"
-)
-
-func TestVoodooSMS_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword voodoosms",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/vouchery/vouchery.go b/pkg/detectors/vouchery/vouchery.go
deleted file mode 100644
index 5e4668b1f1ba..000000000000
--- a/pkg/detectors/vouchery/vouchery.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package vouchery
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"vouchery"}) + `\b([a-z0-9-]{36})\b`)
- subPat = regexp.MustCompile(detectors.PrefixRegex([]string{"vouchery"}) + `\b([a-zA-Z0-9-\S]{2,20})\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{"vouchery"}
-}
-
-// FromData will find and optionally verify Vouchery secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- subMatches := subPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, subMatch := range subMatches {
-
- subMatch := strings.TrimSpace(subMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Vouchery,
- Raw: []byte(resMatch),
- RawV2: []byte(resMatch + subMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s.vouchery.io/api/v2.0/users", subMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Vouchery
-}
-
-func (s Scanner) Description() string {
- return "Vouchery is a service that provides API keys for accessing their promotional and loyalty campaign management platform. These keys can be used to manage and track various promotional activities."
-}
diff --git a/pkg/detectors/vouchery/vouchery_integration_test.go b/pkg/detectors/vouchery/vouchery_integration_test.go
deleted file mode 100644
index 851f4f45b845..000000000000
--- a/pkg/detectors/vouchery/vouchery_integration_test.go
+++ /dev/null
@@ -1,133 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package vouchery
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVouchery_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VOUCHERY")
- subdomain := testSecrets.MustGetField("VOUCHERY_SUBDOMAIN")
- inactiveSecret := testSecrets.MustGetField("VOUCHERY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vouchery secret %s within vouchery %s", secret, subdomain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Vouchery,
- Verified: false,
- RawV2: []byte(secret + "secret"),
- },
- {
- DetectorType: detectorspb.DetectorType_Vouchery,
- Verified: true,
- RawV2: []byte(secret + subdomain),
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vouchery secret %s within vouchery %s but not valid", inactiveSecret, subdomain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Vouchery,
- Verified: false,
- RawV2: []byte(inactiveSecret + "secret"),
- },
- {
- DetectorType: detectorspb.DetectorType_Vouchery,
- Verified: false,
- RawV2: []byte(inactiveSecret + subdomain),
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Vouchery.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Vouchery.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/vouchery/vouchery_test.go b/pkg/detectors/vouchery/vouchery_test.go
deleted file mode 100644
index 81bfcb5d82e8..000000000000
--- a/pkg/detectors/vouchery/vouchery_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package vouchery
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "7ts1czbnd621chpqufnon62o32w0z2iuf15x"
- invalidKey = "7ts1czb?d621chpqufnon62o32w0z2iuf15x"
- validSub = "u;Yb0#E$0"
- invalidSub = "u;Yb?#E$0"
- keyword = "vouchery"
-)
-
-func TestVouchery_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword vouchery",
- input: fmt.Sprintf("%s '%s'\n\n%s '%s'\n", keyword, validKey, keyword, validSub),
- want: []string{validKey + validSub},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s '%s'\n\n%s '%s'\n", keyword, invalidKey, keyword, invalidSub),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/vpnapi/vpnapi.go b/pkg/detectors/vpnapi/vpnapi.go
deleted file mode 100644
index 1a3fc2846ace..000000000000
--- a/pkg/detectors/vpnapi/vpnapi.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package vpnapi
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"vpnapi"}) + `\b([a-z0-9A-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"vpnapi"}
-}
-
-// FromData will find and optionally verify Vpnapi secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Vpnapi,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://vpnapi.io/api/8.8.8.8?key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Vpnapi
-}
-
-func (s Scanner) Description() string {
- return "Vpnapi provides information about IP addresses, including geolocation and VPN detection. Vpnapi keys can be used to access this information."
-}
diff --git a/pkg/detectors/vpnapi/vpnapi_integration_test.go b/pkg/detectors/vpnapi/vpnapi_integration_test.go
deleted file mode 100644
index 34583d05b839..000000000000
--- a/pkg/detectors/vpnapi/vpnapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package vpnapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVpnapi_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VPNAPI")
- inactiveSecret := testSecrets.MustGetField("VPNAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vpnapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Vpnapi,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vpnapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Vpnapi,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Vpnapi.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Vpnapi.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/vpnapi/vpnapi_test.go b/pkg/detectors/vpnapi/vpnapi_test.go
deleted file mode 100644
index 438806f947a7..000000000000
--- a/pkg/detectors/vpnapi/vpnapi_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package vpnapi
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "LAqmsNMbdWkEmZW053axrvWTUKVRbcKN"
- invalidPattern = "LAqmsNMbdWkEmZW0?3axrvWTUKVRbcKN"
- keyword = "vpnapi"
-)
-
-func TestVpnapi_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword vpnapi",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/vultrapikey/vultrapikey.go b/pkg/detectors/vultrapikey/vultrapikey.go
deleted file mode 100644
index bf3cdf303cc2..000000000000
--- a/pkg/detectors/vultrapikey/vultrapikey.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package vultrapikey
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"vultr"}) + `\b([A-Z0-9]{36})\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{"vultr"}
-}
-
-// FromData will find and optionally verify VultrApiKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_VultrApiKey,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.vultr.com/v2/account", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_VultrApiKey
-}
-
-func (s Scanner) Description() string {
- return "Vultr is a cloud service provider offering various services including computing, storage, and networking. Vultr API keys can be used to access and manage these services."
-}
diff --git a/pkg/detectors/vultrapikey/vultrapikey_integration_test.go b/pkg/detectors/vultrapikey/vultrapikey_integration_test.go
deleted file mode 100644
index 575713d4f005..000000000000
--- a/pkg/detectors/vultrapikey/vultrapikey_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package vultrapikey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVultrApiKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VULTR_API_TOKEN")
- inactiveSecret := testSecrets.MustGetField("VULTR_API_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vultrapikey secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VultrApiKey,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vultrapikey secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_VultrApiKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("VultrApiKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("VultrApiKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/vultrapikey/vultrapikey_test.go b/pkg/detectors/vultrapikey/vultrapikey_test.go
deleted file mode 100644
index c894f154dbda..000000000000
--- a/pkg/detectors/vultrapikey/vultrapikey_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package vultrapikey
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "F7CTEQ0FK6XSPCSG24XMBULREVNG4AL3RTQK"
- invalidPattern = "F7CTEQ0FK6?SPCSG24XMBULREVNG4AL3RTQK"
- keyword = "vultrapikey"
-)
-
-func TestVultrApiKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword vultrapikey",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/vyte/vyte.go b/pkg/detectors/vyte/vyte.go
deleted file mode 100644
index e6e423d83eef..000000000000
--- a/pkg/detectors/vyte/vyte.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package vyte
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"vyte"}) + `\b([0-9a-z]{50})\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{"vyte"}
-}
-
-// FromData will find and optionally verify Vyte secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Vyte,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.vyte.in/v2/events", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/vnd.vyte+json; version=3")
- req.Header.Add("Authorization", resMatch)
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Vyte
-}
-
-func (s Scanner) Description() string {
- return "Vyte is a scheduling platform that allows users to create and manage events. Vyte API keys can be used to access and manage these events."
-}
diff --git a/pkg/detectors/vyte/vyte_integration_test.go b/pkg/detectors/vyte/vyte_integration_test.go
deleted file mode 100644
index 7cce3162fdca..000000000000
--- a/pkg/detectors/vyte/vyte_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package vyte
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestVyte_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("VYTE")
- inactiveSecret := testSecrets.MustGetField("VYTE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vyte secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Vyte,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a vyte secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Vyte,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Vyte.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Vyte.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/vyte/vyte_test.go b/pkg/detectors/vyte/vyte_test.go
deleted file mode 100644
index 4fac5b388bf6..000000000000
--- a/pkg/detectors/vyte/vyte_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package vyte
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "ii86dga95chsw6qe6dt0oh7nqn5udhqsk3fhaizktnv770zk81"
- invalidPattern = "ii86dga95chsw6qe6dt0oh7?qn5udhqsk3fhaizktnv770zk81"
- keyword = "vyte"
-)
-
-func TestVyte_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword vyte",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/walkscore/walkscore.go b/pkg/detectors/walkscore/walkscore.go
deleted file mode 100644
index 50c99785401c..000000000000
--- a/pkg/detectors/walkscore/walkscore.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package walkscore
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"walkscore"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"walkscore"}
-}
-
-// FromData will find and optionally verify Walkscore secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_WalkScore,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://transit.walkscore.com/transit/search/stops/?lat=47.6101359&lon=-122.3420567&wsapikey=%s", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
- if (res.StatusCode >= 200 && res.StatusCode < 300) && strings.Contains(body, `distance`) {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_WalkScore
-}
-
-func (s Scanner) Description() string {
- return "Walkscore API keys can be used to access Walkscore's services for retrieving walkability scores and related data."
-}
diff --git a/pkg/detectors/walkscore/walkscore_integration_test.go b/pkg/detectors/walkscore/walkscore_integration_test.go
deleted file mode 100644
index 852b47fbf0a1..000000000000
--- a/pkg/detectors/walkscore/walkscore_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package walkscore
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWalkscore_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WALKSCORE")
- inactiveSecret := testSecrets.MustGetField("WALKSCORE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a walkscore secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WalkScore,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a walkscore secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WalkScore,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Walkscore.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Walkscore.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/walkscore/walkscore_test.go b/pkg/detectors/walkscore/walkscore_test.go
deleted file mode 100644
index 838f33f12ffe..000000000000
--- a/pkg/detectors/walkscore/walkscore_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package walkscore
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "tmexev6bvpc43sk0g0fk3dpft50ss56r"
- invalidPattern = "tmexev6bvpc43sk0?0fk3dpft50ss56r"
- keyword = "walkscore"
-)
-
-func TestWalkscore_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword walkscore",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/weatherbit/weatherbit.go b/pkg/detectors/weatherbit/weatherbit.go
deleted file mode 100644
index 886759bee6a8..000000000000
--- a/pkg/detectors/weatherbit/weatherbit.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package weatherbit
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"weatherbit"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"weatherbit"}
-}
-
-// FromData will find and optionally verify Weatherbit secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_WeatherBit,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.weatherbit.io/v2.0/history/airquality?lat=38.0&lon=-78.0&key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_WeatherBit
-}
-
-func (s Scanner) Description() string {
- return "Weatherbit is a weather data service provider. Weatherbit API keys can be used to access weather data and related services."
-}
diff --git a/pkg/detectors/weatherbit/weatherbit_integration_test.go b/pkg/detectors/weatherbit/weatherbit_integration_test.go
deleted file mode 100644
index 616ac3f00709..000000000000
--- a/pkg/detectors/weatherbit/weatherbit_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package weatherbit
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWeatherbit_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WEATHERBIT")
- inactiveSecret := testSecrets.MustGetField("WEATHERBIT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a weatherbit secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WeatherBit,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a weatherbit secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WeatherBit,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Weatherbit.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Weatherbit.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/weatherbit/weatherbit_test.go b/pkg/detectors/weatherbit/weatherbit_test.go
deleted file mode 100644
index 44d7cb611f75..000000000000
--- a/pkg/detectors/weatherbit/weatherbit_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package weatherbit
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "zqjxo96wmdi1rdcvohnpu0v3bkypbuti"
- invalidPattern = "zqjx?96wmdi1rdcvohnpu0v3bkypbuti"
- keyword = "weatherbit"
-)
-
-func TestWeatherbit_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword weatherbit",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/weatherstack/weatherstack.go b/pkg/detectors/weatherstack/weatherstack.go
deleted file mode 100644
index 6e0b71be851d..000000000000
--- a/pkg/detectors/weatherstack/weatherstack.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package weatherstack
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"weatherstack"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"weatherstack"}
-}
-
-// FromData will find and optionally verify Weatherstack secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_WeatherStack,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.weatherstack.com/current?access_key=%s&query=LA", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
- validResponse := strings.Contains(body, `location`) || strings.Contains(body, `"info":"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption."`)
- if (res.StatusCode >= 200 && res.StatusCode < 300) && validResponse {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_WeatherStack
-}
-
-func (s Scanner) Description() string {
- return "Weatherstack is a weather service that provides real-time weather information. Weatherstack API keys can be used to access weather data from their API."
-}
diff --git a/pkg/detectors/weatherstack/weatherstack_integration_test.go b/pkg/detectors/weatherstack/weatherstack_integration_test.go
deleted file mode 100644
index a671e784d58c..000000000000
--- a/pkg/detectors/weatherstack/weatherstack_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package weatherstack
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWeatherstack_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WEATHERSTACK")
- inactiveSecret := testSecrets.MustGetField("WEATHERSTACK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a weatherstack secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WeatherStack,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a weatherstack secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WeatherStack,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Weatherstack.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Weatherstack.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/weatherstack/weatherstack_test.go b/pkg/detectors/weatherstack/weatherstack_test.go
deleted file mode 100644
index 0e688a48ec9e..000000000000
--- a/pkg/detectors/weatherstack/weatherstack_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package weatherstack
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "w3m1ri2dev8tqm96o8wwxxjh1tutvqvw"
- invalidPattern = "w3m1ri2dev8tqm96?8wwxxjh1tutvqvw"
- keyword = "weatherstack"
-)
-
-func TestWeatherstack_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword weatherstack",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/web3storage/web3storage.go b/pkg/detectors/web3storage/web3storage.go
deleted file mode 100644
index e5d7f9611d8d..000000000000
--- a/pkg/detectors/web3storage/web3storage.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package web3storage
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"web3"}) + `\b(eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.eyJ[A-Za-z0-9-_]{100,300}\.[A-Za-z0-9-_]{25,100})\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{"web3"}
-}
-
-// FromData will find and optionally verify Web3storage secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Web3Storage,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.web3.storage/user/uploads", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %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 if res.StatusCode == 401 {
- // The secret is determinately not verified (nothing to do)
- } else {
- err := fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- s1.SetVerificationError(err, resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Web3Storage
-}
-
-func (s Scanner) Description() string {
- return "Web3.storage is a service offering decentralized storage solutions. Web3.storage API keys can be used to access and manage stored data."
-}
diff --git a/pkg/detectors/web3storage/web3storage_integration_test.go b/pkg/detectors/web3storage/web3storage_integration_test.go
deleted file mode 100644
index 42efd3f7c083..000000000000
--- a/pkg/detectors/web3storage/web3storage_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package web3storage
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWeb3Storage_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WEB3STORAGE")
- inactiveSecret := testSecrets.MustGetField("WEB3STORAGE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a web3storage secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Web3Storage,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a web3storage secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Web3Storage,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a web3storage secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Web3Storage,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a web3storage secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Web3Storage,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Web3Storage.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Web3Storage.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/web3storage/web3storage_test.go b/pkg/detectors/web3storage/web3storage_test.go
deleted file mode 100644
index 0210b71dcf36..000000000000
--- a/pkg/detectors/web3storage/web3storage_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package web3storage
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ5nzGXjcjJ2CaJVf8LF1cNVd07A4XSYHvHK1MQMuum_OZw3ibxmzrTUFYWMvbzzSlJB1jg4E5aT4s6wmpP8OstDNWeHaHuhRBtJss.DuF8uu3Z49HQtAQRG_3r_wCqQ_S-YnFcKrL7vPU1xGtPKxzhK1NbPkZCjXVjGBxseuirozDquv05HxJBScNaQzn"
- invalidPattern = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ5nzGXjcjJ2CaJVf8LF1cNVd07A?XSYHvHK1MQMuum_OZw3ibxmzrTUFYWMvbzzSlJB1jg4E5aT4s6wmpP8OstDNWeHaHuhRBtJss.DuF8uu3Z49HQtAQRG_3r_wCqQ_S-YnFcKrL7vPU1xGtPKxzhK1NbPkZCjXVjGBxseuirozDquv05HxJBScNaQzn"
- keyword = "web3storage"
-)
-
-func TestWeb3Storage_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword web3storage",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/webex/webex.go b/pkg/detectors/webex/webex.go
deleted file mode 100644
index d13ab835f4de..000000000000
--- a/pkg/detectors/webex/webex.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package webex
-
-import (
- "context"
- "encoding/json"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"webex"}) + `\b([a-f0-9]{64})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"webex"}) + `\b(C[a-f0-9]{64})\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{"webex"}
-}
-
-// FromData will find and optionally verify Webex secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- for _, idMatch := range idMatches {
- id := strings.TrimSpace(idMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Webex,
- Raw: []byte(resMatch),
- }
-
- if verify {
- payload := strings.NewReader("grant_type=authorization_code&code=362ad374-735c-4f69-aa8e-bf384f8602de&client_id=" + id + "&client_secret=" + resMatch + "&redirect_uri=http%3A%2F%2Flocalhost.com%2Fb")
- req, err := http.NewRequestWithContext(ctx, "POST", "https://webexapis.com/v1/access_token", payload)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- client := common.SaneHttpClient()
- res, err := client.Do(req)
- if err == nil {
- body, err := io.ReadAll(res.Body)
- res.Body.Close()
- if err == nil {
- var message struct {
- Message string `json:"message"`
- }
- if err := json.Unmarshal(body, &message); err == nil {
- var getError = regexp.MustCompile(detectors.PrefixRegex([]string{"error"}) + `(redirect_uri_mismatch)`)
- result := getError.FindAllStringSubmatch(message.Message, -1)
- if len(result) > 0 {
- s1.Verified = true
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Webex
-}
-
-func (s Scanner) Description() string {
- return "Webex is a collaboration tool that provides video conferencing, online meetings, screen share, and webinars. Webex API keys can be used to access and manage these services."
-}
diff --git a/pkg/detectors/webex/webex_integration_test.go b/pkg/detectors/webex/webex_integration_test.go
deleted file mode 100644
index b2a44e2a58e2..000000000000
--- a/pkg/detectors/webex/webex_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package webex
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWebex_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WEBEX_CLIENT_SECRET")
- inactiveId := testSecrets.MustGetField("WEBEX_CLIENT_ID_INACTIVE")
- id := testSecrets.MustGetField("WEBEX_CLIENT_ID")
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webex secret %s within webex secret %s", id, secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Webex,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webex secret %s within but webex secret %s not valid", inactiveId, secret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Webex,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Webex.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Webex.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/webex/webex_test.go b/pkg/detectors/webex/webex_test.go
deleted file mode 100644
index f161bb75ed9f..000000000000
--- a/pkg/detectors/webex/webex_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package webex
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "b631516f9ef65883d1fa8124a09bfd51209cfb38081eb8e1e9054a37210fdb5a"
- invalidKey = "b631516f9ef65883d1fa81?4a09bfd51209cfb38081eb8e1e9054a37210fdb5a"
- validId = "Cce4c920e27143ee7bef6d57381f468d8dca7ac861f3d1e3f09205ee7a0927b0b"
- invalidId = "Cce4c920e27143ee7bef6d573?1f468d8dca7ac861f3d1e3f09205ee7a0927b0b"
- validGetError = "redirect_uri_mismatch"
- invalidGetError = "redirect_url_mismatch"
- keyword = "webex"
-)
-
-func TestWebex_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword webex",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId, keyword, validGetError),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId, keyword, invalidGetError),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/webexbot/webexbot.go b/pkg/detectors/webexbot/webexbot.go
deleted file mode 100644
index 1dfe4059c621..000000000000
--- a/pkg/detectors/webexbot/webexbot.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package webexbot
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- keyPat = regexp.MustCompile(`([a-zA-Z0-9]{64}_[a-zA-Z0-9]{4}_[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12})`)
-)
-
-// 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{"spark", "webex"}
-}
-
-// FromData will find and optionally verify Webexbot secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- if len(match) > 1 {
- uniqueMatches[match[1]] = struct{}{}
- }
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_WebexBot,
- Raw: []byte(match),
- Redacted: match[:5] + "...",
- ExtraData: map[string]string{},
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-type response struct {
- Type string `json:"type"`
- ID string `json:"id"`
- DisplayName string `json:"displayName"`
- NickName string `json:"nickName"`
- UserName string `json:"username"`
- Emails []string `json:"emails"`
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://webexapis.com/v1/people/me", nil)
- if err != nil {
- return false, nil, err
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Authorization", "Bearer "+token)
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- // parse the response body to json
- var resp response
- extraData := make(map[string]string)
- if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
- return true, nil, fmt.Errorf("failed to decode response: %w", err)
- }
-
- extraData["type"] = resp.Type
- extraData["username"] = resp.UserName
- return true, extraData, nil
- case http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_WebexBot
-}
-
-func (s Scanner) Description() string {
- return "Webex bot allows applications to interact with users in Webex spaces."
-}
diff --git a/pkg/detectors/webexbot/webexbot_integration_test.go b/pkg/detectors/webexbot/webexbot_integration_test.go
deleted file mode 100644
index db0010f61420..000000000000
--- a/pkg/detectors/webexbot/webexbot_integration_test.go
+++ /dev/null
@@ -1,168 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package webexbot
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWebexbot_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WEBEXBOT")
- inactiveSecret := testSecrets.MustGetField("WEBEXBOT_INACTIVE")
-
- redacted := secret[:5] + "..."
- inactiveRedacted := inactiveSecret[:5] + "..."
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webexbot secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WebexBot,
- Verified: true,
- Redacted: redacted,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webexbot secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WebexBot,
- Verified: false,
- Redacted: inactiveRedacted,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webexbot secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WebexBot,
- Verified: false,
- Redacted: redacted,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webexbot secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WebexBot,
- Verified: false,
- Redacted: redacted,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Webexbot.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "primarySecret", "ExtraData")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Webexbot.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/webexbot/webexbot_test.go b/pkg/detectors/webexbot/webexbot_test.go
deleted file mode 100644
index 40b67037311f..000000000000
--- a/pkg/detectors/webexbot/webexbot_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package webexbot
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestWebexbot_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: "webexbot_token = 'ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_asdf_asdkqw34-qwer-vbnm-asdf-qwertyuiopkl'",
- want: []string{"ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_asdf_asdkqw34-qwer-vbnm-asdf-qwertyuiopkl"},
- },
- {
- name: "finds multiple Webex Bot tokens",
- input: `
- webex_bot_token = 'ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_qwer_zxcv1234-asdf-ghjk-tyui-mnbvcxzqwert'
- webexbot = "ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_asdf_qwer4567-qwer-vbnm-asdf-xcvbnmasdfgh"
- `,
- want: []string{
- "ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_qwer_zxcv1234-asdf-ghjk-tyui-mnbvcxzqwert",
- "ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_asdf_qwer4567-qwer-vbnm-asdf-xcvbnmasdfgh",
- },
- },
- {
- name: "does not match invalid token name",
- input: "webex = 'asdf1234qwer5678zxcv9012lkjh_asdf_qwer3456-qwer-vbnm-asdf-zxcvbnmasdfgh'",
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/webflow/webflow.go b/pkg/detectors/webflow/webflow.go
deleted file mode 100644
index 1db49826e9ca..000000000000
--- a/pkg/detectors/webflow/webflow.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package webflow
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"webflow"}) + `\b([a-zA0-9]{64})\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{"webflow"}
-}
-
-// FromData will find and optionally verify Webflow secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Webflow,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.webflow.com/info", nil)
- if err != nil {
- continue
- }
- req.Header.Add("accept-version", "1.0.0")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Webflow
-}
-
-func (s Scanner) Description() string {
- return "Webflow is a web design tool that allows users to design, build, and launch responsive websites visually. Webflow API keys can be used to access and manipulate data within Webflow projects."
-}
diff --git a/pkg/detectors/webflow/webflow_integration_test.go b/pkg/detectors/webflow/webflow_integration_test.go
deleted file mode 100644
index f4c3e7c0eea9..000000000000
--- a/pkg/detectors/webflow/webflow_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package webflow
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWebflow_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WEBFLOW_TOKEN")
- inactiveSecret := testSecrets.MustGetField("WEBFLOW_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webflow secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Webflow,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webflow secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Webflow,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Webflow.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Webflow.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/webflow/webflow_test.go b/pkg/detectors/webflow/webflow_test.go
deleted file mode 100644
index 2a2b58ae7675..000000000000
--- a/pkg/detectors/webflow/webflow_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package webflow
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "nwA9d0mv4juqgv8sqprzlz5yqcfkpv6o3jzlfuxlwugcikyhqpyn3h2ip6ffjbas"
- invalidPattern = "nwA9d0mv4juqgv8sqprzlz5yqcfkpv6o?jzlfuxlwugcikyhqpyn3h2ip6ffjbas"
- keyword = "webflow"
-)
-
-func TestWebflow_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword webflow",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/webscraper/webscraper.go b/pkg/detectors/webscraper/webscraper.go
deleted file mode 100644
index f992f86757f5..000000000000
--- a/pkg/detectors/webscraper/webscraper.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package webscraper
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"webscraper"}) + `\b([a-zA-Z0-9]{60})\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{"webscraper"}
-}
-
-// FromData will find and optionally verify WebScraper secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_WebScraper,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.webscraper.io/api/v1/sitemaps?api_token=%s", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_WebScraper
-}
-
-func (s Scanner) Description() string {
- return "WebScraper is a web scraping service that allows you to extract data from websites. WebScraper API keys can be used to create, manage, and run sitemaps for web scraping tasks."
-}
diff --git a/pkg/detectors/webscraper/webscraper_integration_test.go b/pkg/detectors/webscraper/webscraper_integration_test.go
deleted file mode 100644
index 79efa05e9822..000000000000
--- a/pkg/detectors/webscraper/webscraper_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package webscraper
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWebScraper_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WEBSCRAPER")
- inactiveSecret := testSecrets.MustGetField("WEBSCRAPER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webscraper secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WebScraper,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webscraper secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WebScraper,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("WebScraper.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("WebScraper.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/webscraper/webscraper_test.go b/pkg/detectors/webscraper/webscraper_test.go
deleted file mode 100644
index 094406fc5f25..000000000000
--- a/pkg/detectors/webscraper/webscraper_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package webscraper
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "XEOnTTxcYkZqSEpTs8DVKLahmlp7YtnSfkoCmwI3Wbn5AmlM5TPhaVWxV2Ql"
- invalidPattern = "XEO?TTxcYkZqSEpTs8DVKLahmlp7YtnSfkoCmwI3Wbn5AmlM5TPhaVWxV2Ql"
- keyword = "webscraper"
-)
-
-func TestWebScraper_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword webscraper",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/webscraping/webscraping.go b/pkg/detectors/webscraping/webscraping.go
deleted file mode 100644
index 4e0b1fe34e62..000000000000
--- a/pkg/detectors/webscraping/webscraping.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package webscraping
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"webscraping"}) + `\b([0-9A-Za-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"webscraping"}
-}
-
-// FromData will find and optionally verify Webscraping secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Webscraping,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", `https://api.webscrapingapi.com/v1?api_key=`+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
-
- if strings.Contains(body, "key `url` required.") {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Webscraping
-}
-
-func (s Scanner) Description() string {
- return "Webscraping API keys can be used to access web scraping services to extract data from websites."
-}
diff --git a/pkg/detectors/webscraping/webscraping_integration_test.go b/pkg/detectors/webscraping/webscraping_integration_test.go
deleted file mode 100644
index 26007155afd8..000000000000
--- a/pkg/detectors/webscraping/webscraping_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package webscraping
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWebscraping_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WEBSCRAPING")
- inactiveSecret := testSecrets.MustGetField("WEBSCRAPING_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webscraping secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Webscraping,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a webscraping secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Webscraping,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Webscraping.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Webscraping.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/webscraping/webscraping_test.go b/pkg/detectors/webscraping/webscraping_test.go
deleted file mode 100644
index b556f341164d..000000000000
--- a/pkg/detectors/webscraping/webscraping_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package webscraping
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "lCBYsml2jnyw2gO7lsLj8j6DwIyUXbkE"
- invalidPattern = "?CBYsml2jnyw2gO7lsLj8j6DwIyUXbkE"
- keyword = "webscraping"
-)
-
-func TestWebscraping_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword webscraping",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/websitepulse/websitepulse.go b/pkg/detectors/websitepulse/websitepulse.go
deleted file mode 100644
index 4b6250f5db06..000000000000
--- a/pkg/detectors/websitepulse/websitepulse.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package websitepulse
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"websitepulse"}) + `\b([0-9a-f]{32})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"websitepulse"}) + `\b([0-9a-zA-Z._]{4,22})\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{"websitepulse"}
-}
-
-// FromData will find and optionally verify Websitepulse secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idmatch := range idmatches {
- resIdMatch := strings.TrimSpace(idmatch[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Websitepulse,
- Raw: []byte(resMatch),
- }
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.websitepulse.com/textserver.php?method=GetContacts&username=%s&key=%s", resIdMatch, resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
-
- if strings.Contains(body, "Active") {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Websitepulse
-}
-
-func (s Scanner) Description() string {
- return "Websitepulse is a web-based service that monitors websites and servers. The keys and IDs can be used to access and manage monitoring configurations."
-}
diff --git a/pkg/detectors/websitepulse/websitepulse_integration_test.go b/pkg/detectors/websitepulse/websitepulse_integration_test.go
deleted file mode 100644
index 7d589d5292c8..000000000000
--- a/pkg/detectors/websitepulse/websitepulse_integration_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package websitepulse
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWebsitepulse_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WEBSITEPULSE")
- inactiveSecret := testSecrets.MustGetField("WEBSITEPULSE_INACTIVE")
- id := testSecrets.MustGetField("WEBSITEPULSE_ID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a websitepulse secret %s within websitepulse %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Websitepulse,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Websitepulse,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a websitepulse secret %s within but not valid websitepulse %s", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Websitepulse,
- Verified: false,
- },
- {
- DetectorType: detectorspb.DetectorType_Websitepulse,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Websitepulse.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Websitepulse.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/websitepulse/websitepulse_test.go b/pkg/detectors/websitepulse/websitepulse_test.go
deleted file mode 100644
index bb92f3437996..000000000000
--- a/pkg/detectors/websitepulse/websitepulse_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package websitepulse
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "3bdaacfbb85ee2febeb2b82f37a11d09"
- invalidKey = "3?daacfbb85ee2febeb2b82f37a11d09"
- validId = "E7Yr.IG0P6W2vtOya"
- invalidId = "?7Yr.IG0P6W2vtOy?"
- keyword = "websitepulse"
-)
-
-func TestWebsitepulse_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword websitepulse",
- input: fmt.Sprintf("%s '%s'\n%s '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s '%s'\n%s '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/weightsandbiases/weightsandbiases.go b/pkg/detectors/weightsandbiases/weightsandbiases.go
deleted file mode 100644
index daede1930ad9..000000000000
--- a/pkg/detectors/weightsandbiases/weightsandbiases.go
+++ /dev/null
@@ -1,132 +0,0 @@
-package weightsandbiases
-
-import (
- "bytes"
- "context"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strconv"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{ client *http.Client }
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"wandb"}) + `\b([0-9a-f]{40})\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{"wandb"} }
-
-// FromData will find and optionally verify Weightsandbiases secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_WeightsAndBiases,
- Raw: []byte(match),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-type viewerResponse struct {
- Data struct {
- Viewer struct {
- ID string `json:"id"`
- Username string `json:"username"`
- Email string `json:"email"`
- Admin bool `json:"admin"`
- } `json:"viewer"`
- } `json:"data"`
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
- query := `{"query": "query Viewer { viewer { id username email admin } }"}`
-
- const baseURL = "https://api.wandb.ai/graphql"
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewBufferString(query))
- if err != nil {
- return false, nil, err
- }
-
- authHeader := base64.StdEncoding.EncodeToString([]byte("api:" + token))
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Authorization", "Basic "+authHeader)
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- var viewerResp viewerResponse
- if err := json.NewDecoder(res.Body).Decode(&viewerResp); err != nil {
- return false, nil, err
- }
-
- // Only consider it verified if we got back a username.
- if viewerResp.Data.Viewer.Username == "" {
- return false, nil, nil
- }
-
- extraData := map[string]string{
- "username": viewerResp.Data.Viewer.Username,
- "email": viewerResp.Data.Viewer.Email,
- "admin": strconv.FormatBool(viewerResp.Data.Viewer.Admin),
- }
- return true, extraData, nil
- case http.StatusUnauthorized:
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Description() string {
- return "Weights & Biases is a Machine Learning Operations (MLOps) platform that helps track experiments, version datasets, evaluate model performance, and collaborate with team members"
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_WeightsAndBiases
-}
diff --git a/pkg/detectors/weightsandbiases/weightsandbiases_integration_test.go b/pkg/detectors/weightsandbiases/weightsandbiases_integration_test.go
deleted file mode 100644
index 73a96f45fd39..000000000000
--- a/pkg/detectors/weightsandbiases/weightsandbiases_integration_test.go
+++ /dev/null
@@ -1,167 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package weightsandbiases
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWeightsandbiases_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WEIGHTSANDBIASES")
- inactiveSecret := testSecrets.MustGetField("WEIGHTSANDBIASES_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a weightsandbiases secret wandb %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WeightsAndBiases,
- Verified: true,
- ExtraData: map[string]string{
- "admin": "false",
- "email": "source-integrations@trufflesec.com",
- "username": "source-integrations",
- },
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a weightsandbiases secret wandb %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WeightsAndBiases,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a weightsandbiases secret wandb %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WeightsAndBiases,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a weightsandbiases secret wandb %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WeightsAndBiases,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Weightsandbiases.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Weightsandbiases.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/weightsandbiases/weightsandbiases_test.go b/pkg/detectors/weightsandbiases/weightsandbiases_test.go
deleted file mode 100644
index 28adaf9a14bd..000000000000
--- a/pkg/detectors/weightsandbiases/weightsandbiases_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package weightsandbiases
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-func TestWeightsandbiases_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: "WANDB_API_KEY = 'eedf1c984f6b995ec40ecc6658356044847ffb31'",
- want: []string{"eedf1c984f6b995ec40ecc6658356044847ffb31"},
- },
- {
- name: "finds all matches",
- input: `WANDB_API_KEY = 'eedf1c984f6b995ec40ecc6658356044847ffb31'
-WANDB_API_KEY= 'eedf1c984f6b995ec40ecc6658356044847ffb32'`,
- want: []string{"eedf1c984f6b995ec40ecc6658356044847ffb31", "eedf1c984f6b995ec40ecc6658356044847ffb32"},
- },
- {
- name: "invalid pattern",
- input: "WANDB_API_KEY = 'e84f6b995ec40ecc6658356044847ffb31'",
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/wepay/wepay.go b/pkg/detectors/wepay/wepay.go
deleted file mode 100644
index b2c21abd0c13..000000000000
--- a/pkg/detectors/wepay/wepay.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package wepay
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
-
- appIDPat = regexp.MustCompile(`\b(\d{6})\b`)
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"wepay"}) + `\b([a-zA-Z0-9_?]{62})\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{"wepay"}
-}
-
-// FromData will find and optionally verify WePay secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- appIDmatches := appIDPat.FindAllStringSubmatch(dataStr, -1)
-
- resAppIDMatch := ""
- for _, appIDMatch := range appIDmatches {
- resAppIDMatch = strings.TrimSpace(appIDMatch[1])
- }
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_WePay,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://stage-api.wepay.com/payments?type=credit_card&credit_card=4003830171874018", nil)
- if err != nil {
- continue
- }
- req.Header.Add("App-Token", resMatch)
- req.Header.Add("App-Id", resAppIDMatch)
- req.Header.Add("Api-Version", "3.0")
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Unique-Key", "Unique-Key0")
-
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_WePay
-}
-
-func (s Scanner) Description() string {
- return "WePay is an online payment service provider. WePay API keys can be used to process payments and access account information."
-}
diff --git a/pkg/detectors/wepay/wepay_integration_test.go b/pkg/detectors/wepay/wepay_integration_test.go
deleted file mode 100644
index 6f427a27b455..000000000000
--- a/pkg/detectors/wepay/wepay_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package wepay
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWePay_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WEPAY_TOKEN")
- inactiveSecret := testSecrets.MustGetField("WEPAY_INACTIVE")
- appID := testSecrets.MustGetField("WEPAY_APPID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wepay token %s and app ID %s within ", secret, appID)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WePay,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wepay secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WePay,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("WePay.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("WePay.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/wepay/wepay_test.go b/pkg/detectors/wepay/wepay_test.go
deleted file mode 100644
index 651c38a4b319..000000000000
--- a/pkg/detectors/wepay/wepay_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package wepay
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validAppID = "135827"
- invalidAppID = "135?27"
- validKey = "JFdESNcC4meVkWFygHV5O5FmhycSCsQnlmmXWwTKSQOJG3ERd?stTjE8m98hJU"
- invalidKey = "JFdESNcC4meVkWFygHV5O5FmhycSCsQ!lmmXWwTKSQOJG3ERd?stTjE8m98hJU"
- keyword = "wepay"
-)
-
-func TestWePay_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword wepay",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validAppID, keyword, validKey),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidAppID, keyword, invalidKey),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/whoxy/whoxy.go b/pkg/detectors/whoxy/whoxy.go
deleted file mode 100644
index e358a89096c5..000000000000
--- a/pkg/detectors/whoxy/whoxy.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package whoxy
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "io"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"whoxy"}) + `\b([0-9a-z]{33})\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{"whoxy"}
-}
-
-// FromData will find and optionally verify Whoxy secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Whoxy,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.whoxy.com/?key=%s&account=balance", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- continue
- }
- body := string(bodyBytes)
- if res.StatusCode >= 200 && res.StatusCode < 300 && strings.Contains(body, `"status": 1`) {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Whoxy
-}
-
-func (s Scanner) Description() string {
- return "Whoxy is a service used to retrieve WHOIS data for domain names. Whoxy API keys can be used to query WHOIS information."
-}
diff --git a/pkg/detectors/whoxy/whoxy_integration_test.go b/pkg/detectors/whoxy/whoxy_integration_test.go
deleted file mode 100644
index 6043566cce9f..000000000000
--- a/pkg/detectors/whoxy/whoxy_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package whoxy
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWhoxy_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WHOXY")
- inactiveSecret := testSecrets.MustGetField("WHOXY_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a whoxy secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Whoxy,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a whoxy secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Whoxy,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Whoxy.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Whoxy.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/whoxy/whoxy_test.go b/pkg/detectors/whoxy/whoxy_test.go
deleted file mode 100644
index 5db060da7177..000000000000
--- a/pkg/detectors/whoxy/whoxy_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package whoxy
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "ev1avuvxo6rnn2dlof3r1vesz4xyvawit"
- invalidPattern = "ev1avuvx?6rnn2dlof3r1vesz4xyvawit"
- keyword = "whoxy"
-)
-
-func TestWhoxy_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword whoxy",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/wistia/wistia.go b/pkg/detectors/wistia/wistia.go
deleted file mode 100644
index 93806bb1bc45..000000000000
--- a/pkg/detectors/wistia/wistia.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package wistia
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"wistia"}) + `\b([0-9a-z]{64})\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{"wistia"}
-}
-
-// FromData will find and optionally verify Wistia secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Wistia,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.wistia.com/v1/stats/account.json?access_token="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Wistia
-}
-
-func (s Scanner) Description() string {
- return "Wistia is a video hosting platform designed for businesses. Wistia API keys can be used to access and manage video content and account settings."
-}
diff --git a/pkg/detectors/wistia/wistia_integration_test.go b/pkg/detectors/wistia/wistia_integration_test.go
deleted file mode 100644
index aa9713bb5b3f..000000000000
--- a/pkg/detectors/wistia/wistia_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package wistia
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWistia_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WISTIA")
- inactiveSecret := testSecrets.MustGetField("WISTIA_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wistia secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Wistia,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wistia secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Wistia,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Wistia.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Wistia.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/wistia/wistia_test.go b/pkg/detectors/wistia/wistia_test.go
deleted file mode 100644
index a92b777b90e2..000000000000
--- a/pkg/detectors/wistia/wistia_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package wistia
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "epg6wik0b273hrk8ubc3xk8e00yg0o22c9l1prumz0ag4dz6ht4zgj99uq0kuor8"
- invalidPattern = "epg6wik0b273hrk8ub?3xk8e00yg0o22c9l1prumz0ag4dz6ht4zgj99uq0kuor8"
- keyword = "wistia"
-)
-
-func TestWistia_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword wistia",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/wit/wit.go b/pkg/detectors/wit/wit.go
deleted file mode 100644
index 0da015d27219..000000000000
--- a/pkg/detectors/wit/wit.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package wit
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"wit"}) + `\b([A-Z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"wit"}
-}
-
-// FromData will find and optionally verify Wit secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Wit,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.wit.ai/message?q=saascndncdcdksCHDKSCVSDCasdasdVCSDVCSDAVHKCDCVHKSADVCKDVKCDSVHCSACVHJDSCVJHSADCVJHSAJ", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Wit
-}
-
-func (s Scanner) Description() string {
- return "Wit.ai is a natural language processing service that provides an API for building conversational interfaces. Wit.ai API keys can be used to access and modify this data and compute."
-}
diff --git a/pkg/detectors/wit/wit_integration_test.go b/pkg/detectors/wit/wit_integration_test.go
deleted file mode 100644
index 1e2903f515f9..000000000000
--- a/pkg/detectors/wit/wit_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package wit
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWit_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WIT")
- inactiveSecret := testSecrets.MustGetField("WIT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wit secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Wit,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wit secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Wit,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Wit.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Wit.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/wit/wit_test.go b/pkg/detectors/wit/wit_test.go
deleted file mode 100644
index 393caa4a9584..000000000000
--- a/pkg/detectors/wit/wit_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package wit
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "1ZLU5OXGEYX56K3IOL1Z88Y5YU5CC1KK"
- invalidPattern = "1ZLU5?XGEYX56K3IOL1Z88Y5YU5CC1KK"
- keyword = "wit"
-)
-
-func TestWit_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword wit",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/wiz/wiz.go b/pkg/detectors/wiz/wiz.go
deleted file mode 100644
index 862aa9038a4c..000000000000
--- a/pkg/detectors/wiz/wiz.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package wiz
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"wiz"}) + `\b([a-zA-Z0-9]{53})\b`)
- secretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"wiz"}) + `\b([a-zA-Z0-9]{64})\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{"wiz"}
-}
-
-// FromData will find and optionally verify Wiz secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- idMatches := make(map[string]struct{})
- for _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {
- idMatches[match[1]] = struct{}{}
- }
-
- secretMatches := make(map[string]struct{})
- for _, match := range secretPat.FindAllStringSubmatch(dataStr, -1) {
- secretMatches[match[1]] = struct{}{}
- }
-
- for idMatch := range idMatches {
- for secretMatch := range secretMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Wiz,
- Raw: []byte(idMatch),
- RawV2: []byte(idMatch + secretMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, idMatch, secretMatch)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, idMatch, secretMatch)
- }
-
- results = append(results, s1)
-
- // If we've found a verified match with this ID, we don't need to look for anymore. So move on to the next ID.
- if s1.Verified {
- break
- }
- }
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, clientID, clientSecret string) (bool, map[string]string, error) {
- authData := url.Values{}
- authData.Set("grant_type", "client_credentials")
- authData.Set("audience", "wiz-api")
- authData.Set("client_id", clientID)
- authData.Set("client_secret", clientSecret)
-
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://auth.app.wiz.io/oauth/token", strings.NewReader(authData.Encode()))
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Add("Encoding", "UTF-8")
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- // If the endpoint returns useful information, we can return it as a map.
- return true, nil, nil
- } else if res.StatusCode == 401 {
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- } else {
- err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- return false, nil, err
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Wiz
-}
-
-func (s Scanner) Description() string {
- return "Wiz is a cloud security platform. Wiz credentials can be used to access and manage cloud security configurations."
-}
diff --git a/pkg/detectors/wiz/wiz_integration_test.go b/pkg/detectors/wiz/wiz_integration_test.go
deleted file mode 100644
index 022aff779d16..000000000000
--- a/pkg/detectors/wiz/wiz_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package wiz
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWiz_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WIZ")
- inactiveSecret := testSecrets.MustGetField("WIZ_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wiz secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Wiz,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wiz secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Wiz,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wiz secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Wiz,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wiz secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Wiz,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Wiz.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Wiz.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/wiz/wiz_test.go b/pkg/detectors/wiz/wiz_test.go
deleted file mode 100644
index bf831edc8c48..000000000000
--- a/pkg/detectors/wiz/wiz_test.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package wiz
-
-import (
- "context"
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
- "testing"
-)
-
-func TestWiz_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: `
-wiz_client_id = 'lmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDblmSlx1fe6yCfwAbDa8pMp'
-wiz_client_secret = 'lmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDblmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDb'
-`,
- want: []string{
- "lmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDblmSlx1fe6yCfwAbDa8pMp" +
- "lmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDblmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDb",
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/worksnaps/worksnaps.go b/pkg/detectors/worksnaps/worksnaps.go
deleted file mode 100644
index df509bdc4065..000000000000
--- a/pkg/detectors/worksnaps/worksnaps.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package worksnaps
-
-import (
- "context"
- b64 "encoding/base64"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"worksnaps"}) + `\b([0-9A-Za-z]{40})\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{"worksnaps"}
-}
-
-// FromData will find and optionally verify Worksnaps secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Worksnaps,
- Raw: []byte(resMatch),
- }
-
- if verify {
- data := fmt.Sprintf("%s:ignored", resMatch)
- sEnc := b64.StdEncoding.EncodeToString([]byte(data))
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.worksnaps.com/api/projects.xml", nil)
- if err != nil {
- continue
- }
- 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
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Worksnaps
-}
-
-func (s Scanner) Description() string {
- return "Worksnaps is a time tracking service that helps manage and monitor remote work. Worksnaps API keys can be used to access and manage project data and time tracking information."
-}
diff --git a/pkg/detectors/worksnaps/worksnaps_integration_test.go b/pkg/detectors/worksnaps/worksnaps_integration_test.go
deleted file mode 100644
index 59f40b9b9449..000000000000
--- a/pkg/detectors/worksnaps/worksnaps_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package worksnaps
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWorksnaps_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WORKSNAPS")
- inactiveSecret := testSecrets.MustGetField("WORKSNAPS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a worksnaps secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Worksnaps,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a worksnaps secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Worksnaps,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Worksnaps.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Worksnaps.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/worksnaps/worksnaps_test.go b/pkg/detectors/worksnaps/worksnaps_test.go
deleted file mode 100644
index 48dfab4d75fc..000000000000
--- a/pkg/detectors/worksnaps/worksnaps_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package worksnaps
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "YrJteqAWhy6iB8sds3g8LOWopzyUqiyoglEaP9zD"
- invalidPattern = "YrJteqAWhy6iB8sds3g8?OWopzyUqiyoglEaP9zD"
- keyword = "worksnaps"
-)
-
-func TestWorksnaps_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword worksnaps",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/workstack/workstack.go b/pkg/detectors/workstack/workstack.go
deleted file mode 100644
index 8dbaac53683a..000000000000
--- a/pkg/detectors/workstack/workstack.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package workstack
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"workstack"}) + `\b([0-9Aa-zA-Z]{60})\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{"workstack"}
-}
-
-// FromData will find and optionally verify Workstack secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Workstack,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://app.workstack.io/api/team?api_token="+resMatch, nil)
- if err != nil {
- continue
- }
- req.Header.Add("Accept", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Workstack
-}
-
-func (s Scanner) Description() string {
- return "Workstack is a project management tool. Workstack API keys can be used to access and modify project data and manage tasks."
-}
diff --git a/pkg/detectors/workstack/workstack_integration_test.go b/pkg/detectors/workstack/workstack_integration_test.go
deleted file mode 100644
index 5256925ec7eb..000000000000
--- a/pkg/detectors/workstack/workstack_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package workstack
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWorkstack_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WORKSTACK")
- inactiveSecret := testSecrets.MustGetField("WORKSTACK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a workstack secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Workstack,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a workstack secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Workstack,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Workstack.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Workstack.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/workstack/workstack_test.go b/pkg/detectors/workstack/workstack_test.go
deleted file mode 100644
index 7c1465b40824..000000000000
--- a/pkg/detectors/workstack/workstack_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package workstack
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "X6JXXwZhJEOwpIdSAlTZm71XkZFjoLbagkybF4A8U3CiKjPsiIih0xAvQbYB"
- invalidPattern = "X6JXXwZhJEOwpIdSAlTZm71XkZFjoL?agkybF4A8U3CiKjPsiIih0xAvQbYB"
- keyword = "workstack"
-)
-
-func TestWorkstack_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword workstack",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/worldcoinindex/worldcoinindex.go b/pkg/detectors/worldcoinindex/worldcoinindex.go
deleted file mode 100644
index b20766ae28c1..000000000000
--- a/pkg/detectors/worldcoinindex/worldcoinindex.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package worldcoinindex
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"worldcoinindex"}) + `\b([a-zA-Z0-9]{35})\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{"worldcoinindex"}
-}
-
-// FromData will find and optionally verify WorldCoinIndex secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_WorldCoinIndex,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://www.worldcoinindex.com/apiservice/ticker?key=%s&label=ethbtc-ltcbtc&fiat=btc", resMatch), nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- bodyBytes, err := io.ReadAll(res.Body)
- if err == nil {
- bodyString := string(bodyBytes)
- validResponse := strings.Contains(bodyString, `"Markets"`)
-
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- if validResponse {
- s1.Verified = true
- } else {
- s1.Verified = false
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_WorldCoinIndex
-}
-
-func (s Scanner) Description() string {
- return "WorldCoinIndex is a cryptocurrency market data provider. API keys from WorldCoinIndex can be used to access market data and other information."
-}
diff --git a/pkg/detectors/worldcoinindex/worldcoinindex_integration_test.go b/pkg/detectors/worldcoinindex/worldcoinindex_integration_test.go
deleted file mode 100644
index c60caf931ea9..000000000000
--- a/pkg/detectors/worldcoinindex/worldcoinindex_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package worldcoinindex
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWorldCoinIndex_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WORLDCOININDEX")
- inactiveSecret := testSecrets.MustGetField("WORLDCOININDEX_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a worldcoinindex secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WorldCoinIndex,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a worldcoinindex secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WorldCoinIndex,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("WorldCoinIndex.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("WorldCoinIndex.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/worldcoinindex/worldcoinindex_test.go b/pkg/detectors/worldcoinindex/worldcoinindex_test.go
deleted file mode 100644
index 9ca3855135be..000000000000
--- a/pkg/detectors/worldcoinindex/worldcoinindex_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package worldcoinindex
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "CQ4tKlZUi74jEckzgck4tTdXlLodOqtHdos"
- invalidPattern = "CQ4tKlZUi74jE?kzgck4tTdXlLodOqtHdos"
- keyword = "worldcoinindex"
-)
-
-func TestWorldCoinIndex_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword worldcoinindex",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/worldweather/worldweather.go b/pkg/detectors/worldweather/worldweather.go
deleted file mode 100644
index f7e732be188b..000000000000
--- a/pkg/detectors/worldweather/worldweather.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package worldweather
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"worldweather"}) + `\b([0-9a-z]{31})\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{"worldweather"}
-}
-
-// FromData will find and optionally verify Worldweather secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_WorldWeather,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.worldweatheronline.com/premium/v1/search.ashx?query=LA&key=%s", resMatch), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_WorldWeather
-}
-
-func (s Scanner) Description() string {
- return "Worldweather provides weather data services through its API. The keys can be used to access various weather data endpoints."
-}
diff --git a/pkg/detectors/worldweather/worldweather_integration_test.go b/pkg/detectors/worldweather/worldweather_integration_test.go
deleted file mode 100644
index 8d4e84997e85..000000000000
--- a/pkg/detectors/worldweather/worldweather_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package worldweather
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWorldweather_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WORLDWEATHER")
- inactiveSecret := testSecrets.MustGetField("WORLDWEATHER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a worldweather secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WorldWeather,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a worldweather secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_WorldWeather,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Worldweather.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Worldweather.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/worldweather/worldweather_test.go b/pkg/detectors/worldweather/worldweather_test.go
deleted file mode 100644
index bdccde9d448a..000000000000
--- a/pkg/detectors/worldweather/worldweather_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package worldweather
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "ld9oqdefulr8099jt5tdejksjz7aivh"
- invalidPattern = "ld9oqdefulr8099?t5tdejksjz7aivh"
- keyword = "worldweather"
-)
-
-func TestWorldweather_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword worldweather",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/wrike/wrike.go b/pkg/detectors/wrike/wrike.go
deleted file mode 100644
index d9fb23be4d95..000000000000
--- a/pkg/detectors/wrike/wrike.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package wrike
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"wrike"}) + `\b(ey[a-zA-Z0-9-._]{333})\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{"wrike"}
-}
-
-// FromData will find and optionally verify Wrike secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Wrike,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://app-us2.wrike.com/api/v4/contacts?me=true", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Wrike
-}
-
-func (s Scanner) Description() string {
- return "Wrike is a collaborative work management platform that helps teams organize and manage their work. Wrike API keys can be used to access and modify data within Wrike."
-}
diff --git a/pkg/detectors/wrike/wrike_integration_test.go b/pkg/detectors/wrike/wrike_integration_test.go
deleted file mode 100644
index bce287b565a2..000000000000
--- a/pkg/detectors/wrike/wrike_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package wrike
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestWrike_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("WRIKE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("WRIKE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wrike secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Wrike,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a wrike secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Wrike,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Wrike.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Wrike.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/wrike/wrike_test.go b/pkg/detectors/wrike/wrike_test.go
deleted file mode 100644
index 49720af70df2..000000000000
--- a/pkg/detectors/wrike/wrike_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package wrike
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "eyHrUMIkHqmEhqDBTWcuk5kL.tRiP-uQD9ZZSHfYYzdqRYHgkfiPEplgL1INSyA_c9VgbpVIzvFhVx0vzWzhxv9CdyLTr6FCvmDDrxM5BU0gQdyDGgXosinkyl4Z_7OIbHY08sImeavc-fxQ1OzVo6mqb-bDmxwuVWIE5WXb2bcRRnKotFAa4.pCkZAYDxZd66Yt6fK7HM33G6Rr.ALuDQg2mFdemMy1_WIOh9-0l6b6iP2anAp0CbxpcXlrecGHaMgrgzla41NrfpzWjAKlOwq.hbIKdPLLK0bOvg2m6ETkKdGhfRRHkYAqyYO.EhONITs48Eb21kDH0gc"
- invalidPattern = "ey?rUMIkHqmEhqDBTWcuk5kL.tRiP-uQD9ZZSHfYYzdqRYHgkfiPEplgL1INSyA_c9VgbpVIzvFhVx0vzWzhxv9CdyLTr6FCvmDDrxM5BU0gQdyDGgXosinkyl4Z_7OIbHY08sImeavc-fxQ1OzVo6mqb-bDmxwuVWIE5WXb2bcRRnKotFAa4.pCkZAYDxZd66Yt6fK7HM33G6Rr.ALuDQg2mFdemMy1_WIOh9-0l6b6iP2anAp0CbxpcXlrecGHaMgrgzla41NrfpzWjAKlOwq.hbIKdPLLK0bOvg2m6ETkKdGhfRRHkYAqyYO.EhONITs48Eb21kDH0gc"
- keyword = "wrike"
-)
-
-func TestWrike_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword wrike",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/xai/xai.go b/pkg/detectors/xai/xai.go
deleted file mode 100644
index c02203256c26..000000000000
--- a/pkg/detectors/xai/xai.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package xai
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(xai-[0-9a-zA-Z_]{80})\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{"xai-"}
-}
-
-// FromData will find and optionally verify Xai secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keyMatches := make(map[string]struct{})
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatches[match[1]] = struct{}{}
- }
-
- for match := range keyMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_XAI,
- Raw: []byte(match),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, extraData, verificationErr := verifyMatch(ctx, client, match)
- s1.Verified = isVerified
- s1.ExtraData = extraData
- s1.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, s1)
- }
-
- return
-}
-
-func verifyMatch(ctx context.Context, client *http.Client, apiKey string) (bool, map[string]string, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.x.ai/v1/api-key", nil)
- if err != nil {
- return false, nil, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", "Bearer "+apiKey)
-
- res, err := client.Do(req)
- if err != nil {
- return false, nil, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- // Parse the API response for useful information like name and ACLs
- var data struct {
- Name string `json:"name"`
- Acls []string `json:"acls"`
- }
- if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
- // The API Key is still verified, but there are parsing errors.
- // Hence, return true for verified along with error.
- return true, nil, fmt.Errorf("failed to decode response: %w", err)
- }
-
- aclsStr := strings.Join(data.Acls, ",")
-
- // Convert the relevant fields into a map
- result := map[string]string{
- "name": data.Name,
- "acls": aclsStr,
- }
-
- return true, result, nil
- case http.StatusBadRequest, http.StatusUnauthorized:
- // The secret is determinately not verified (nothing to do)
- return false, nil, nil
- default:
- return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_XAI
-}
-
-func (s Scanner) Description() string {
- return "xAI is an AI company with the mission of advancing scientific discovery and gaining a deeper understanding of our universe."
-}
diff --git a/pkg/detectors/xai/xai_integration_test.go b/pkg/detectors/xai/xai_integration_test.go
deleted file mode 100644
index 5bf5309f838b..000000000000
--- a/pkg/detectors/xai/xai_integration_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package xai
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestXai_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("XAI")
- inactiveSecret := testSecrets.MustGetField("XAI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a xai secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_XAI,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a xai secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_XAI,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a xai secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_XAI,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a xai secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_XAI,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Xai.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "ExtraData")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Xai.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/xai/xai_test.go b/pkg/detectors/xai/xai_test.go
deleted file mode 100644
index 3b8251c66799..000000000000
--- a/pkg/detectors/xai/xai_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package xai
-
-import (
- "context"
- "github.com/google/go-cmp/cmp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
- "testing"
-)
-
-func TestXai_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern",
- input: "xai_token = 'xai-W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab'",
- want: []string{"xai-W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab"},
- },
- {
- name: "finds all matches",
- input: `grok_token1 = 'xai-W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab'
-xai_token2 = 'xai-W5zbfUkzlXedo7qD42AbBLlRSsyJr1W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab'`,
- want: []string{"xai-W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab", "xai-W5zbfUkzlXedo7qD42AbBLlRSsyJr1W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab"},
- },
- {
- name: "invalid pattern",
- input: "xai_token = 'xai-W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXe'",
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/yandex/yandex.go b/pkg/detectors/yandex/yandex.go
deleted file mode 100644
index ef2e3deb378c..000000000000
--- a/pkg/detectors/yandex/yandex.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package yandex
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"yandex"}) + `\b([a-z0-9A-Z.]{83})\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{"yandex"}
-}
-
-// FromData will find and optionally verify Yandex secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Yandex,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://dictionary.yandex.net/api/v1/dicservice.json/getLangs?key="+resMatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Yandex
-}
-
-func (s Scanner) Description() string {
- return "Yandex is a technology company that builds intelligent products and services powered by machine learning. Yandex API keys can be used to access and interact with various Yandex services."
-}
diff --git a/pkg/detectors/yandex/yandex_integration_test.go b/pkg/detectors/yandex/yandex_integration_test.go
deleted file mode 100644
index a8e8d0cae4e3..000000000000
--- a/pkg/detectors/yandex/yandex_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package yandex
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestYandex_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("YANDEX")
- inactiveSecret := testSecrets.MustGetField("YANDEX_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a yandex secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Yandex,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a yandex secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Yandex,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Yandex.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Yandex.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/yandex/yandex_test.go b/pkg/detectors/yandex/yandex_test.go
deleted file mode 100644
index d02614f14b33..000000000000
--- a/pkg/detectors/yandex/yandex_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package yandex
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "oNKE6BzKsFuO8qvuau.kqvuAuEkFUS5hoEo.rpoD0siIHlzm305uKw6kFDbzT8p8KSHZGs5iVQ0TPDin4Xt"
- invalidPattern = "oNKE6BzK?FuO8qvuau.kqvuAuEkFUS5hoEo.rpoD0siIHlzm305uKw6kFDbzT8p8KSHZGs5iVQ0TPDin4Xt"
- keyword = "yandex"
-)
-
-func TestYandex_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword yandex",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/yelp/yelp.go b/pkg/detectors/yelp/yelp.go
deleted file mode 100644
index cb0c37408e48..000000000000
--- a/pkg/detectors/yelp/yelp.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package yelp
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"yelp"}) + `\b([a-zA-Z0-9_\\=\.\-]{128})\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{"yelp"}
-}
-
-// FromData will find and optionally verify Yelp secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Yelp,
- Raw: []byte(resMatch),
- }
-
- if verify {
-
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.yelp.com/v3/businesses/search?term=delis&latitude=37.786882&longitude=-122.399972", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %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 if res.StatusCode == 401 || res.StatusCode == 403 {
- // The secret is determinately not verified (nothing to do)
- } else {
- s1.SetVerificationError(fmt.Errorf("unexpected HTTP response status %d", res.StatusCode), resMatch)
- }
- } else {
- s1.SetVerificationError(err, resMatch)
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Yelp
-}
-
-func (s Scanner) Description() string {
- return "Yelp API keys allow access to Yelp's business data and services. Unauthorized access can lead to data breaches and misuse of Yelp's services."
-}
diff --git a/pkg/detectors/yelp/yelp_integration_test.go b/pkg/detectors/yelp/yelp_integration_test.go
deleted file mode 100644
index a659c6f8859b..000000000000
--- a/pkg/detectors/yelp/yelp_integration_test.go
+++ /dev/null
@@ -1,162 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package yelp
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestYelp_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("YELP")
- inactiveSecret := testSecrets.MustGetField("YELP_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
-
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a yelp secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Yelp,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a yelp secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Yelp,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a yelp secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Yelp,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a yelp secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Yelp,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := tt.s
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Yelp.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
-
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("SlackWebhook.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/yelp/yelp_test.go b/pkg/detectors/yelp/yelp_test.go
deleted file mode 100644
index e9d812ba7ba7..000000000000
--- a/pkg/detectors/yelp/yelp_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package yelp
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "YPtzmBZBThgFVginAOiy_.PirbsD-U5rf=L9HUXS-ZdKhyqACHEig8n0raz9ucrqyXp=afbP5FczH3TtSp7XxyxqVA3LVMIGRaxdFw-yIBbsJYMRDchqHpg1=F=irXh1"
- invalidPattern = "Y?tzmBZBThgFVginAOiy_.PirbsD-U5rf=L9HUXS-ZdKhyqACHEig8n0raz9ucrqyXp=afbP5FczH3TtSp7XxyxqVA3LVMIGRaxdFw-yIBbsJYMRDchqHpg1=F=irXh1"
- keyword = "yelp"
-)
-
-func TestYelp_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword yelp",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/youneedabudget/youneedabudget.go b/pkg/detectors/youneedabudget/youneedabudget.go
deleted file mode 100644
index 896b19ae03d3..000000000000
--- a/pkg/detectors/youneedabudget/youneedabudget.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package youneedabudget
-
-import (
- "context"
- "fmt"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"youneedabudget"}) + `\b([0-9a-f]{64})\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{"youneedabudget"}
-}
-
-// FromData will find and optionally verify YouNeedABudget secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_YouNeedABudget,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://api.youneedabudget.com/v1/user", nil)
- if err != nil {
- continue
- }
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_YouNeedABudget
-}
-
-func (s Scanner) Description() string {
- return "YouNeedABudget is a budgeting tool that allows users to manage their personal finances. The API keys can be used to access and modify a user's financial data."
-}
diff --git a/pkg/detectors/youneedabudget/youneedabudget_integration_test.go b/pkg/detectors/youneedabudget/youneedabudget_integration_test.go
deleted file mode 100644
index d292e99f5fe0..000000000000
--- a/pkg/detectors/youneedabudget/youneedabudget_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package youneedabudget
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestYouNeedABudget_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("YOUNEEDABUDGET")
- inactiveSecret := testSecrets.MustGetField("YOUNEEDABUDGET_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a youneedabudget secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_YouNeedABudget,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a youneedabudget secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_YouNeedABudget,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("YouNeedABudget.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("YouNeedABudget.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/youneedabudget/youneedabudget_test.go b/pkg/detectors/youneedabudget/youneedabudget_test.go
deleted file mode 100644
index 37410b571799..000000000000
--- a/pkg/detectors/youneedabudget/youneedabudget_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package youneedabudget
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "1f80bdfa73f8e9e50445de3a5a52fbe585fafe0e26d6fccf090b11153775c43d"
- invalidPattern = "1f?0bdfa73f8e9e50445de3a5a52fbe585fafe0e26d6fccf090b11153775c43d"
- keyword = "youneedabudget"
-)
-
-func TestYouNeedABudget_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword youneedabudget",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/yousign/yousign.go b/pkg/detectors/yousign/yousign.go
deleted file mode 100644
index 8e0fecc1ab4c..000000000000
--- a/pkg/detectors/yousign/yousign.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package yousign
-
-import (
- "context"
- "fmt"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// docs: https://dev.yousign.com/#api-v3-documentation-new
-const PROD_URL = "https://api.yousign.com"
-const STAGING_URL = "https://staging-api.yousign.com"
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"yousign"}) + `\b([0-9a-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"yousign"}
-}
-
-// FromData will find and optionally verify Yousign secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_YouSign,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/users", PROD_URL), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %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 {
- req, err = http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/users", STAGING_URL), nil)
- if err != nil {
- continue
- }
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
- res, err = client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
- }
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_YouSign
-}
-
-func (s Scanner) Description() string {
- return "Yousign is an electronic signature service used to sign and manage documents online. Yousign API keys can be used to access and manage these documents."
-}
diff --git a/pkg/detectors/yousign/yousign_integration_test.go b/pkg/detectors/yousign/yousign_integration_test.go
deleted file mode 100644
index 5d65724fc849..000000000000
--- a/pkg/detectors/yousign/yousign_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package yousign
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestYousign_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("YOUSIGN")
- inactiveSecret := testSecrets.MustGetField("YOUSIGN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a yousign secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_YouSign,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a yousign secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_YouSign,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Yousign.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Yousign.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/yousign/yousign_test.go b/pkg/detectors/yousign/yousign_test.go
deleted file mode 100644
index 58924c24dd16..000000000000
--- a/pkg/detectors/yousign/yousign_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package yousign
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "1opov9oy6ycsi675cyj2gp3jkjt8pw7d"
- invalidPattern = "1opov9oy6y?si675cyj2gp3jkjt8pw7d"
- keyword = "yousign"
-)
-
-func TestYousign_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword yousign",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/youtubeapikey/youtubeapikey.go b/pkg/detectors/youtubeapikey/youtubeapikey.go
deleted file mode 100644
index 94de2994b0b8..000000000000
--- a/pkg/detectors/youtubeapikey/youtubeapikey.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package youtubeapikey
-
-import (
- "context"
- regexp "github.com/wasilibs/go-re2"
- "net/http"
- "strings"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"youtube"}) + `\b([a-zA-Z-0-9_]{39})\b`)
- idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"youtube"}) + `\b([a-zA-Z-0-9]{24})\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{"youtube"}
-}
-
-// FromData will find and optionally verify YoutubeApiKey secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- idmatches := idPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- for _, idmatch := range idmatches {
- resIdmatch := strings.TrimSpace(idmatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_YoutubeApiKey,
- Raw: []byte(resMatch),
- }
-
- if verify {
- req, err := http.NewRequestWithContext(ctx, "GET", "https://www.googleapis.com/youtube/v3/channelSections?key="+resMatch+"&channelId="+resIdmatch, nil)
- if err != nil {
- continue
- }
- res, err := client.Do(req)
- if err == nil {
- defer res.Body.Close()
- if res.StatusCode >= 200 && res.StatusCode < 300 {
- s1.Verified = true
- }
- }
- }
-
- results = append(results, s1)
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_YoutubeApiKey
-}
-
-func (s Scanner) Description() string {
- return "YouTube API Keys allow access to various functionalities of the YouTube Data API, enabling operations such as retrieving video details and managing playlists."
-}
diff --git a/pkg/detectors/youtubeapikey/youtubeapikey_integration_test.go b/pkg/detectors/youtubeapikey/youtubeapikey_integration_test.go
deleted file mode 100644
index f1954f8318dd..000000000000
--- a/pkg/detectors/youtubeapikey/youtubeapikey_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package youtubeapikey
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestYoutubeApiKey_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("YOUTUBEAPIKEY_TOKEN")
- inactiveSecret := testSecrets.MustGetField("YOUTUBEAPIKEY_INACTIVE")
- id := testSecrets.MustGetField("YOUTUBEAPIKEY_CHANNELID")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a youtube secret %s within youtube %s", secret, id)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_YoutubeApiKey,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a youtube secret %s within but not youtube %s valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_YoutubeApiKey,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("YoutubeApiKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("YoutubeApiKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/youtubeapikey/youtubeapikey_test.go b/pkg/detectors/youtubeapikey/youtubeapikey_test.go
deleted file mode 100644
index da6a255c190c..000000000000
--- a/pkg/detectors/youtubeapikey/youtubeapikey_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package youtubeapikey
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "8SN8OtkzJ6z2tcFtv93Gl63o97LGGBvYAyJviDg"
- invalidKey = "8SN8OtkzJ6z2tcFtv93?l63o97LGGBvYAyJviDg"
- validId = "0ifNmkGT6biPToj9TDGYqyFP"
- invalidId = "0ifNmkG?6biPToj9TDGYqyFP"
- keyword = "youtubeapikey"
-)
-
-func TestYoutubeApiKey_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword youtubeapikey",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId),
- want: []string{validKey},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zapierwebhook/zapierwebhook.go b/pkg/detectors/zapierwebhook/zapierwebhook.go
deleted file mode 100644
index 204fc20d7bb7..000000000000
--- a/pkg/detectors/zapierwebhook/zapierwebhook.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package zapierwebhook
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`(https:\/\/hooks\.zapier\.com\/hooks\/catch\/[A-Za-z0-9\/]{16})`)
-)
-
-// 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{"hooks.zapier.com/hooks/catch/"}
-}
-
-// FromData will find and optionally verify ZapierWebhook secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ZapierWebhook,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZapierWebhook(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ZapierWebhook
-}
-
-func (s Scanner) Description() string {
- return "Zapier is an automation tool that connects your apps and services. Zapier webhooks can be used to automate workflows by sending HTTP requests to a unique URL."
-}
-
-func verifyZapierWebhook(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://hooks.zapier.com/hooks/catch/"+key, http.NoBody)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zapierwebhook/zapierwebhook_integration_test.go b/pkg/detectors/zapierwebhook/zapierwebhook_integration_test.go
deleted file mode 100644
index fd6e9d0c08cb..000000000000
--- a/pkg/detectors/zapierwebhook/zapierwebhook_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zapierwebhook
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZapierWebhook_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZAPIERWEBHOOK_TOKEN")
- // inactiveSecret := testSecrets.MustGetField("ZAPIERWEBHOOK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zapierwebhook secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZapierWebhook,
- Verified: true,
- },
- },
- wantErr: false,
- },
- // {
- // name: "found, unverified",
- // s: Scanner{},
- // args: args{
- // ctx: context.Background(),
- // data: []byte(fmt.Sprintf("You can find a zapierwebhook secret %s within but not valid",inactiveSecret)), // the secret would satisfy the regex but not pass validation
- // verify: true,
- // },
- // want: []detectors.Result{
- // {
- // DetectorType: detectorspb.DetectorType_ZapierWebhook,
- // Verified: false,
- // },
- // },
- // wantErr: false,
- // },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ZapierWebhook.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ZapierWebhook.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zapierwebhook/zapierwebhook_test.go b/pkg/detectors/zapierwebhook/zapierwebhook_test.go
deleted file mode 100644
index c3c97dd521bd..000000000000
--- a/pkg/detectors/zapierwebhook/zapierwebhook_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package zapierwebhook
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "https://hooks.zapier.com/hooks/catch/bJdWL5uSLdrqauDu"
- invalidPattern = "https://hooks.zapier.com/hooks/catch/b=?WL5uSLdrqauDu"
- keyword = "zapierwebhook"
-)
-
-func TestZapierWebhook_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zapierwebhook",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zendeskapi/zendeskapi.go b/pkg/detectors/zendeskapi/zendeskapi.go
deleted file mode 100644
index e1efe925a855..000000000000
--- a/pkg/detectors/zendeskapi/zendeskapi.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package zendeskapi
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = detectors.DetectorHttpClientWithNoLocalAddresses
-
- token = regexp.MustCompile(detectors.PrefixRegex([]string{"zendesk"}) + `([A-Za-z0-9_-]{40})`)
- email = regexp.MustCompile(`\b([a-zA-Z-0-9-]{5,16}\@[a-zA-Z-0-9]{4,16}\.[a-zA-Z-0-9]{3,6})\b`)
- domain = regexp.MustCompile(`\b([a-zA-Z-0-9]{3,25}\.zendesk\.com)\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{"zendesk"}
-}
-
-// FromData will find and optionally verify ZendeskApi secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- var uniqueEmails, uniqueTokens, uniqueDomains = make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})
-
- for _, match := range email.FindAllStringSubmatch(dataStr, -1) {
- uniqueEmails[match[1]] = struct{}{}
- }
-
- for _, match := range token.FindAllStringSubmatch(dataStr, -1) {
- uniqueTokens[match[1]] = struct{}{}
- }
-
- for _, match := range domain.FindAllStringSubmatch(dataStr, -1) {
- uniqueDomains[match[1]] = struct{}{}
- }
-
- for token := range uniqueTokens {
- for email := range uniqueEmails {
- for domain := range uniqueDomains {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ZendeskApi,
- Raw: []byte(token),
- }
-
- if verify {
- isVerified, verificationErr := verifyZendesk(ctx, client, email, token, domain)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, token)
- }
-
- results = append(results, s1)
-
- }
- }
-
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ZendeskApi
-}
-
-func (s Scanner) Description() string {
- return "Zendesk is a customer service platform. Zendesk API tokens can be used to access and modify customer service data."
-}
-
-func verifyZendesk(ctx context.Context, client *http.Client, email, token, domain string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+domain+"/api/v2/users.json", http.NoBody)
- if err != nil {
- return false, err
- }
-
- // docs: https://developer.zendesk.com/api-reference/introduction/security-and-auth/
- req.SetBasicAuth(email+"/token", token)
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusNotFound:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zendeskapi/zendeskapi_integration_test.go b/pkg/detectors/zendeskapi/zendeskapi_integration_test.go
deleted file mode 100644
index e53b877d2bdd..000000000000
--- a/pkg/detectors/zendeskapi/zendeskapi_integration_test.go
+++ /dev/null
@@ -1,119 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zendeskapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZendeskApi_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- token := testSecrets.MustGetField("ZENDESKAPI_TOKEN")
- inactiveSecret := testSecrets.MustGetField("ZENDESKAPI_INACTIVE")
- email := testSecrets.MustGetField("ZENDESK_EMAIL")
- domain := testSecrets.MustGetField("ZENDESK_DOMAIN")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zendesk secret %s within zendesk %s with zendesk %s", token, email, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZendeskApi,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zendeskapi secret %s within zendesk %s but not zendesk %s valid", inactiveSecret, email, domain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZendeskApi,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ZendeskApi.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ZendeskApi.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zendeskapi/zendeskapi_test.go b/pkg/detectors/zendeskapi/zendeskapi_test.go
deleted file mode 100644
index a24989909012..000000000000
--- a/pkg/detectors/zendeskapi/zendeskapi_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package zendeskapi
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validToken = "9kXe6PUOsazCNF48SK8pzNluINpVQzuuqTArJC9X"
- invalidToken = "9kXe6PUOsa?CNF48SK8pzNluINpVQzuuqTArJC9X"
- validEmail = "qT0pYx@yV8H7EmhFDbvH4j.com"
- invalidEmail = "qT0pYx@yV8H7Em?FDbvH4j.com"
- validDomain = "yF5j0x7.zendesk.com"
- invalidDomain = "yF5j0x7.z?ndesk.com"
- keyword = "zendeskapi"
-)
-
-func TestZendeskApi_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zendeskapi",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, validToken, keyword, validEmail, keyword, validDomain),
- want: []string{validToken},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, invalidToken, keyword, invalidEmail, keyword, invalidDomain),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zenkitapi/zenkitapi.go b/pkg/detectors/zenkitapi/zenkitapi.go
deleted file mode 100644
index 3b3450b7a466..000000000000
--- a/pkg/detectors/zenkitapi/zenkitapi.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package zenkitapi
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zenkit"}) + `\b([0-9a-z]{8}\-[0-9A-Za-z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"zenkit"}
-}
-
-// FromData will find and optionally verify ZenkitAPI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ZenkitAPI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZenkitKey(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ZenkitAPI
-}
-
-func (s Scanner) Description() string {
- return "Zenkit is a collaborative SaaS platform for project management, database building, and more. Zenkit API keys can be used to access and interact with Zenkit's services programmatically."
-}
-
-func verifyZenkitKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://base.zenkit.com/api/v1/users/me", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Zenkit-API-Key", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zenkitapi/zenkitapi_integration_test.go b/pkg/detectors/zenkitapi/zenkitapi_integration_test.go
deleted file mode 100644
index 3ebfaeb83076..000000000000
--- a/pkg/detectors/zenkitapi/zenkitapi_integration_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zenkitapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZenkitAPI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZENKITAPI")
- inactiveSecret := testSecrets.MustGetField("ZENKITAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zenkit secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZenkitAPI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zenkit secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZenkitAPI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ZenkitAPI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ZenkitAPI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- s.FromData(ctx, false, data)
- }
- })
- }
-}
diff --git a/pkg/detectors/zenkitapi/zenkitapi_test.go b/pkg/detectors/zenkitapi/zenkitapi_test.go
deleted file mode 100644
index 6c141e8b62c9..000000000000
--- a/pkg/detectors/zenkitapi/zenkitapi_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package zenkitapi
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "6fcehxlk-zTFk2oYJb7YS6Dx2RnU0UIyXHwQ922Ln"
- invalidPattern = "6fcehxlk-zTFk2oYJb7Y?6Dx2RnU0UIyXHwQ922Ln"
- keyword = "zenkitapi"
-)
-
-func TestZenkitAPI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zenkitapi",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zenrows/zenrows.go b/pkg/detectors/zenrows/zenrows.go
deleted file mode 100644
index 3ceb3c8f6af5..000000000000
--- a/pkg/detectors/zenrows/zenrows.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package zenrows
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zenrows"}) + `\b([0-9a-f]{40})\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{"zenrows"}
-}
-
-// FromData will find and optionally verify ZenRows secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ZenRows,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZenrowsKey(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ZenRows
-}
-
-func (s Scanner) Description() string {
- return "ZenRows is a web scraping API service that allows users to extract data from websites. ZenRows API keys can be used to access and scrape web data."
-}
-
-func verifyZenrowsKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://api.zenrows.com/v1/?apikey=%s&url=https://httpbin.org/anything", key), http.NoBody)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusPaymentRequired: // docs: https://docs.zenrows.com/api-error-codes#402-payment-required
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zenrows/zenrows_integration_test.go b/pkg/detectors/zenrows/zenrows_integration_test.go
deleted file mode 100644
index 94cf14219218..000000000000
--- a/pkg/detectors/zenrows/zenrows_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zenrows
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZenRows_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZENROWS")
- inactiveSecret := testSecrets.MustGetField("ZENROWS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zenrows secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZenRows,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zenrows secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZenRows,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ZenRows.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ZenRows.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zenrows/zenrows_test.go b/pkg/detectors/zenrows/zenrows_test.go
deleted file mode 100644
index aa0881767e0f..000000000000
--- a/pkg/detectors/zenrows/zenrows_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package zenrows
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "1122be45ae1bdd11f40d1ed29a25cbdcc70920c7"
- invalidPattern = "?122be45ae1bdd11f40d1ed29a25cbdcc70920c7"
- keyword = "zenrows"
-)
-
-func TestZenRows_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zenrows",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zenscrape/zenscrape.go b/pkg/detectors/zenscrape/zenscrape.go
deleted file mode 100644
index 36345587996d..000000000000
--- a/pkg/detectors/zenscrape/zenscrape.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package zenscrape
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zenscrape"}) + `\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\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{"zenscrape"}
-}
-
-// FromData will find and optionally verify Zenscrape secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Zenscrape,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZenScrapeKey(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Zenscrape
-}
-
-func (s Scanner) Description() string {
- return "Zenscrape is a web scraping service that provides an API to extract data from websites. Zenscrape API keys can be used to access and scrape data from web pages."
-}
-
-func verifyZenScrapeKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.zenscrape.com/api/v1/status", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("apikey", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK, http.StatusTooManyRequests:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zenscrape/zenscrape_integration_test.go b/pkg/detectors/zenscrape/zenscrape_integration_test.go
deleted file mode 100644
index d5ff34af2ab7..000000000000
--- a/pkg/detectors/zenscrape/zenscrape_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zenscrape
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZenscrape_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZENSCRAPE")
- inactiveSecret := testSecrets.MustGetField("ZENSCRAPE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zenscrape secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Zenscrape,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zenscrape secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Zenscrape,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Zenscrape.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Zenscrape.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zenscrape/zenscrape_test.go b/pkg/detectors/zenscrape/zenscrape_test.go
deleted file mode 100644
index ece51f9e8edf..000000000000
--- a/pkg/detectors/zenscrape/zenscrape_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package zenscrape
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "3e687efb-1561-ecee-3d5a-e989224e7276"
- invalidPattern = "3e687efb?1561-ecee-3d5a-e989224e7276"
- keyword = "zenscrape"
-)
-
-func TestZenscrape_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zenscrape",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zenserp/zenserp.go b/pkg/detectors/zenserp/zenserp.go
deleted file mode 100644
index 19d66704cb86..000000000000
--- a/pkg/detectors/zenserp/zenserp.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package zenserp
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClientTimeOut(5 * time.Second)
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zenserp"}) + `\b([0-9a-z-]{36})\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{"zenserp"}
-}
-
-// FromData will find and optionally verify Zenserp secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Zenserp,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZenSerpKey(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Zenserp
-}
-
-func (s Scanner) Description() string {
- return "Zenserp is a service that provides SERP (Search Engine Results Page) API. Zenserp API keys can be used to access search engine data programmatically."
-}
-
-func verifyZenSerpKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, `https://app.zenserp.com/api/v2/search?q="test"&apikey=`+key, http.NoBody)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, err
- }
-
- body := string(bodyBytes)
- if strings.Contains(body, "query") {
- return true, nil
- }
-
- return false, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zenserp/zenserp_integration_test.go b/pkg/detectors/zenserp/zenserp_integration_test.go
deleted file mode 100644
index 89394bee7791..000000000000
--- a/pkg/detectors/zenserp/zenserp_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zenserp
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZenserp_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZENSERP")
- inactiveSecret := testSecrets.MustGetField("ZENSERP_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zenserp secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Zenserp,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zenserp secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Zenserp,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Zenserp.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Zenserp.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zenserp/zenserp_test.go b/pkg/detectors/zenserp/zenserp_test.go
deleted file mode 100644
index 351b2363237d..000000000000
--- a/pkg/detectors/zenserp/zenserp_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package zenserp
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "nleq2hhp9tdp-x8qfucd1t028118t5mk0qge"
- invalidPattern = "nleq2hhp9tdp-x8qfu?d1t028118t5mk0qge"
- keyword = "zenserp"
-)
-
-func TestZenserp_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zenserp",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zeplin/zeplin.go b/pkg/detectors/zeplin/zeplin.go
deleted file mode 100644
index feb516c5925e..000000000000
--- a/pkg/detectors/zeplin/zeplin.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package zeplin
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zeplin"}) + `\b([a-zA-Z0-9-.]{350,400})\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{"zeplin"}
-}
-
-// FromData will find and optionally verify Zeplin secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Zeplin,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZeplinKey(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Zeplin
-}
-
-func (s Scanner) Description() string {
- return "Zeplin is a collaboration app for UI designers and front-end developers. Zeplin API keys can be used to access and modify project data."
-}
-
-func verifyZeplinKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.zeplin.dev/v1/users/me", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zeplin/zeplin_integration_test.go b/pkg/detectors/zeplin/zeplin_integration_test.go
deleted file mode 100644
index 46d1359916ae..000000000000
--- a/pkg/detectors/zeplin/zeplin_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zeplin
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZeplin_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZEPLIN_TOKEN")
- inactiveSecret := testSecrets.MustGetField("ZEPLIN_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zeplin secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Zeplin,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zeplin secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Zeplin,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Zeplin.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Zeplin.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zeplin/zeplin_test.go b/pkg/detectors/zeplin/zeplin_test.go
deleted file mode 100644
index 9353a289d822..000000000000
--- a/pkg/detectors/zeplin/zeplin_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package zeplin
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "bv9qqMUYcE-wGLV4SGIFrlQ2OKRWMC.w18-Mpt3ZfQ3We.RrE1Cpx9-o50H5DFyPlLNhIBni20i6uelit3U5zJf-kxOY2UZjEOw3Nn1I..gsJbav9MhwiwQqITI8Z3vrOKpYLZ6vwib4bfU.NFMhmyxUUsRuOR4whWubCyV9-K7NPQwhRbKgaQJ7iWIx1eJPTD7Hzanr92YpeexiglRULihOhEQWAhbSqt87i5YQBwfQ-s06KsGMmRUgx0DyRbcoo6vzlM7FV.fS7jzWDoSxas1rMBUtp7s9p3gNY77wBdvVm6HrTCy3t85PMzjSZkN6cck44XXFo8KZ3ItVTWcvIxF.7qLTnL"
- invalidPattern = "b 9qqMUYcE-wGLV4SGIFrlQ2OKRWMC.w18-Mpt3ZfQ3We.RrE1Cpx9-o50H5DFyPlLNhIBni20i6uelit3U5zJf-kxOY2UZjEOw3Nn1I..gsJbav9MhwiwQqITI8Z3vrOKpYLZ6vwib4bfU.NFMhmyxUUsRuOR4whWubCyV9-K7NPQwhRbKgaQJ7iWIx1eJPTD7Hzanr92YpeexiglRULihOhEQWAhbSqt87i5YQBwfQ-s06KsGMmRUgx0DyRbcoo6vzlM7FV.fS7jzWDoSxas1rMBUtp7s9p3gNY77wBdvVm6HrTCy3t85PMzjSZkN6cck44XXFo8KZ3ItVTWcvIxF.7qLTnL"
- keyword = "zeplin"
-)
-
-func TestZeplin_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zeplin",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zerobounce/zerobounce.go b/pkg/detectors/zerobounce/zerobounce.go
deleted file mode 100644
index 727297048e1a..000000000000
--- a/pkg/detectors/zerobounce/zerobounce.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package zerobounce
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zerobounce"}) + `\b([a-z0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"zerobounce"}
-}
-
-// FromData will find and optionally verify Zerobounce secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Zerobounce,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZeroBounceKey(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Zerobounce
-}
-
-func (s Scanner) Description() string {
- return "Zerobounce is an email validation and verification service. Zerobounce API keys can be used to access and utilize this service."
-}
-
-func verifyZeroBounceKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.zerobounce.net/v2/activity?email=testemail@email.com&api_key="+key, http.NoBody)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- bodyBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return false, err
- }
-
- if strings.Contains(string(bodyBytes), "found") {
- return true, nil
- }
-
- return false, nil
- case http.StatusUnauthorized, http.StatusNotFound:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zerobounce/zerobounce_integration_test.go b/pkg/detectors/zerobounce/zerobounce_integration_test.go
deleted file mode 100644
index 98aa5287fb72..000000000000
--- a/pkg/detectors/zerobounce/zerobounce_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zerobounce
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZerobounce_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZEROBOUNCE_TOKEN")
- inactiveSecret := testSecrets.MustGetField("ZEROBOUNCE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zerobounce secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Zerobounce,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zerobounce secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Zerobounce,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Zerobounce.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Zerobounce.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zerobounce/zerobounce_test.go b/pkg/detectors/zerobounce/zerobounce_test.go
deleted file mode 100644
index 82b9f366f849..000000000000
--- a/pkg/detectors/zerobounce/zerobounce_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package zerobounce
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "dclyjj1w13g1hwkny2qz47ba6zq10fpn"
- invalidPattern = "dcl?jj1w13g1hwkny2qz47ba6zq10fpn"
- keyword = "zerobounce"
-)
-
-func TestZerobounce_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zerobounce",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zerotier/zerotier.go b/pkg/detectors/zerotier/zerotier.go
deleted file mode 100644
index a227be6d55ea..000000000000
--- a/pkg/detectors/zerotier/zerotier.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package zerotier
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zerotier"}) + `\b([0-9a-zA-Z]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"zerotier"}
-}
-
-// FromData will find and optionally verify Zerotier secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ZeroTier,
- Raw: []byte(resMatch),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyZerotierKey(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ZeroTier
-}
-
-func (s Scanner) Description() string {
- return "ZeroTier is a network virtualization technology that provides secure and flexible network connections. ZeroTier API keys can be used to manage and control these network connections."
-}
-
-// API docs: https://docs.zerotier.com/central/v1/
-func verifyZerotierKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.zerotier.com/api/v1/network", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Authorization", fmt.Sprintf("token %s", key))
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zerotier/zerotier_integration_test.go b/pkg/detectors/zerotier/zerotier_integration_test.go
deleted file mode 100644
index 24c37dcfe27e..000000000000
--- a/pkg/detectors/zerotier/zerotier_integration_test.go
+++ /dev/null
@@ -1,127 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zerotier
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZerotier_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZEROTIER")
- inactiveSecret := testSecrets.MustGetField("ZEROTIER_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zerotier secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZeroTier,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zerotier secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZeroTier,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Zerotier.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "primarySecret")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Zerotier.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zerotier/zerotier_test.go b/pkg/detectors/zerotier/zerotier_test.go
deleted file mode 100644
index a9fd37244cc5..000000000000
--- a/pkg/detectors/zerotier/zerotier_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package zerotier
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "AiO4lTwWm7vdj2D0Zf3xdQbQNN3tSyJB"
- invalidPattern = "AiO4lTwWm7vdj2D0?f3xdQbQNN3tSyJB"
- keyword = "zerotier"
-)
-
-func TestZerotier_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zerotier",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zipapi/zipapi.go b/pkg/detectors/zipapi/zipapi.go
deleted file mode 100644
index c4e4c716fab2..000000000000
--- a/pkg/detectors/zipapi/zipapi.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package zipapi
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zipapi"}) + `\b([A-Z0-9a-z]{32})\b`)
- emailPat = regexp.MustCompile(common.EmailPattern)
- pwordPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zipapi"}) + `\b([a-zA-Z0-9!=@#$%^]{7,})`)
-)
-
-// 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{"zipapi"}
-}
-
-// FromData will find and optionally verify Zipapi secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- uniqueEmailMatches, uniqueKeyMatches, uniquePassMatches := make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})
- for _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueKeyMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for _, match := range pwordPat.FindAllStringSubmatch(dataStr, -1) {
- uniquePassMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for keyMatch := range uniqueKeyMatches {
- for emailMatch := range uniqueEmailMatches {
- for passMatch := range uniquePassMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ZipAPI,
- Raw: []byte(keyMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZipAPI(ctx, client, emailMatch, keyMatch, passMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, keyMatch)
- }
-
- results = append(results, s1)
- }
- }
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ZipAPI
-}
-
-func (s Scanner) Description() string {
- return "ZipAPI is a service used to retrieve ZIP code information. ZipAPI keys can be used to access and retrieve this information from their API."
-}
-
-func verifyZipAPI(ctx context.Context, client *http.Client, email, key, password string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://service.zipapi.us/zipcode/90210/?X-API-KEY=%s", key), http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.SetBasicAuth(email, password)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zipapi/zipapi_integration_test.go b/pkg/detectors/zipapi/zipapi_integration_test.go
deleted file mode 100644
index 2c58af37e11c..000000000000
--- a/pkg/detectors/zipapi/zipapi_integration_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zipapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZipapi_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZIPAPI")
- email := testSecrets.MustGetField("SCANNERS_EMAIL")
- pword := testSecrets.MustGetField("SCANNERS_PASS")
- inactiveSecret := testSecrets.MustGetField("ZIPAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zipapi secret %s within zipapi email %s and zipapi password %s", secret, email, pword)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZipAPI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zipapi secret %s within zipapi email %s and zipapi password %s but not valid", inactiveSecret, email, pword)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZipAPI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Zipapi.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Zipapi.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zipapi/zipapi_test.go b/pkg/detectors/zipapi/zipapi_test.go
deleted file mode 100644
index 58c6b242d9b6..000000000000
--- a/pkg/detectors/zipapi/zipapi_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package zipapi
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = `
- zipapi_key = zipapiabcdef1234567890abcdef1234
- zipapi_email = zipapi_user@example.com
- zipapi_pass = zipapiSecurePass123!
- `
- invalidPattern = "abcde/testing@go"
-)
-
-func TestZipapi_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern",
- input: validPattern,
- want: []string{
- "zipapiabcdef1234567890abcdef1234",
- "zipapiabcdef1234567890abcdef1234",
- "zipapiabcdef1234567890abcdef1234",
- },
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("zipapi: %s", invalidPattern),
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 && test.want != nil {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- t.Errorf("expected %d results, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zipbooks/zipbooks.go b/pkg/detectors/zipbooks/zipbooks.go
deleted file mode 100644
index d67ef9bda5c0..000000000000
--- a/pkg/detectors/zipbooks/zipbooks.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package zipbooks
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- emailPat = regexp.MustCompile(common.EmailPattern)
- pwordPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zipbooks", "password"}) + `\b([a-zA-Z0-9!=@#$%^]{8,})`)
-)
-
-// 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{"zipbooks"}
-}
-
-// FromData will find and optionally verify Zipbooks secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- pwordMatches := pwordPat.FindAllStringSubmatch(dataStr, -1)
-
- uniqueEmailMatches := make(map[string]struct{})
- for _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}
- }
-
- for emailMatch := range uniqueEmailMatches {
- for _, pwordMatch := range pwordMatches {
- resPword := strings.TrimSpace(pwordMatch[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ZipBooks,
- Raw: []byte(emailMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZipBooksCredentials(ctx, client, emailMatch, resPword)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, emailMatch)
- }
-
- results = append(results, s1)
- }
- }
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ZipBooks
-}
-
-func (s Scanner) Description() string {
- return "ZipBooks is an accounting software service that allows businesses to manage their finances online. The credentials can be used to access and manage financial data."
-}
-
-func verifyZipBooksCredentials(ctx context.Context, client *http.Client, email, password string) (bool, error) {
- payload := strings.NewReader(fmt.Sprintf(`{"email": "%s", "password": "%s"}`, email, password))
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.zipbooks.com/v2/auth/login", payload)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Content-Type", "application/json")
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusNotFound: // username or password not found
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zipbooks/zipbooks_integration_test.go b/pkg/detectors/zipbooks/zipbooks_integration_test.go
deleted file mode 100644
index 1e5e4532738a..000000000000
--- a/pkg/detectors/zipbooks/zipbooks_integration_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zipbooks
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZipbooks_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- email := testSecrets.MustGetField("SCANNERS_EMAIL")
- pword := testSecrets.MustGetField("SCANNERS_PASS")
- inactivePass := testSecrets.MustGetField("SCANNERS_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zipbooks email %s within zipbooks password %s", email, pword)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZipBooks,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zipbooks email %s within zipbooks password %s but not valid", email, inactivePass)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZipBooks,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Zipbooks.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Zipbooks.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zipbooks/zipbooks_test.go b/pkg/detectors/zipbooks/zipbooks_test.go
deleted file mode 100644
index 83a04f3eae57..000000000000
--- a/pkg/detectors/zipbooks/zipbooks_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package zipbooks
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "SecureP@ss123 / admin@secure.com"
- invalidPattern = "abcde/testing@go"
-)
-
-func TestZipBooks_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
-
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - zipbooks keyword",
- input: fmt.Sprintf("zipbooks = %s", validPattern),
- want: []string{"admin@secure.com"},
- },
- {
- name: "valid pattern - password keyword",
- input: fmt.Sprintf("zipbooks-password: %s", validPattern),
- want: []string{"admin@secure.com"},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("zipbooks: %s", invalidPattern),
- want: nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 && test.want != nil {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- t.Errorf("expected %d results, got %d", len(test.want), len(results))
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zipcodeapi/zipcodeapi.go b/pkg/detectors/zipcodeapi/zipcodeapi.go
deleted file mode 100644
index b8b0083a6eab..000000000000
--- a/pkg/detectors/zipcodeapi/zipcodeapi.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package zipcodeapi
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zipcodeapi"}) + `\b([a-zA-Z0-9]{64})\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{"zipcodeapi"}
-}
-
-// FromData will find and optionally verify ZipCodeAPI secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ZipCodeAPI,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZipCodeAPIKey(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ZipCodeAPI
-}
-
-func (s Scanner) Description() string {
- return "ZipCodeAPI provides a service for retrieving zip code information and calculating distances between zip codes. ZipCodeAPI keys can be used to access these services."
-}
-
-func verifyZipCodeAPIKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://www.zipcodeapi.com/rest/%s/distance.json/71601/72959/mile", key), http.NoBody)
- if err != nil {
- return false, err
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zipcodeapi/zipcodeapi_integration_test.go b/pkg/detectors/zipcodeapi/zipcodeapi_integration_test.go
deleted file mode 100644
index ad30fe1e626e..000000000000
--- a/pkg/detectors/zipcodeapi/zipcodeapi_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zipcodeapi
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZipCodeAPI_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZIPCODEAPI")
- inactiveSecret := testSecrets.MustGetField("ZIPCODEAPI_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zipcodeapi secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZipCodeAPI,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zipcodeapi secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZipCodeAPI,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ZipCodeAPI.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ZipCodeAPI.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zipcodeapi/zipcodeapi_test.go b/pkg/detectors/zipcodeapi/zipcodeapi_test.go
deleted file mode 100644
index 778d150cbbb1..000000000000
--- a/pkg/detectors/zipcodeapi/zipcodeapi_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package zipcodeapi
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "8SOdOO0FIJrmO5iNYhyInMT0Dm81a5MHO9ZKwGlkdX04dBwLmrVZtm4JLaGm2Ulq"
- invalidPattern = "8S?dOO0FIJrmO5iNYhyInMT0Dm81a5MHO9ZKwGlkdX04dBwLmrVZtm4JLaGm2Ulq"
- keyword = "zipcodeapi"
-)
-
-func TestZipCodeAPI_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zipcodeapi",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zipcodebase/zipcodebase.go b/pkg/detectors/zipcodebase/zipcodebase.go
deleted file mode 100644
index ebcdf18bde54..000000000000
--- a/pkg/detectors/zipcodebase/zipcodebase.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package zipcodebase
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zipcodebase"}) + `\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\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{"zipcodebase"}
-}
-
-// FromData will find and optionally verify Zipcodebase secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_Zipcodebase,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZipCodeBaseKey(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_Zipcodebase
-}
-
-func (s Scanner) Description() string {
- return "Zipcodebase is a service that provides access to a database of postal codes. The API keys can be used to query this database for information related to postal codes."
-}
-
-func verifyZipCodeBaseKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.zipcodebase.com/api/v1/search?codes=10005,10006", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Accept", "application/vnd.zipcodebase+json; version=3")
- req.Header.Add("apikey", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized, http.StatusForbidden:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zipcodebase/zipcodebase_integration_test.go b/pkg/detectors/zipcodebase/zipcodebase_integration_test.go
deleted file mode 100644
index 6fc9570958bd..000000000000
--- a/pkg/detectors/zipcodebase/zipcodebase_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zipcodebase
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZipcodebase_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZIPCODEBASE")
- inactiveSecret := testSecrets.MustGetField("ZIPCODEBASE_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zipcodebase secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Zipcodebase,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zipcodebase secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_Zipcodebase,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Zipcodebase.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("Zipcodebase.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zipcodebase/zipcodebase_test.go b/pkg/detectors/zipcodebase/zipcodebase_test.go
deleted file mode 100644
index 57a3d62a0fe7..000000000000
--- a/pkg/detectors/zipcodebase/zipcodebase_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package zipcodebase
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "aef47d5e-b057-711f-066c-3a45682a3dd7"
- invalidPattern = "aef47d5e?b057-711f-066c-3a45682a3dd7"
- keyword = "zipcodebase"
-)
-
-func TestZipcodebase_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zipcodebase",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zohocrm/zohocrm.go b/pkg/detectors/zohocrm/zohocrm.go
deleted file mode 100644
index bab3e9423676..000000000000
--- a/pkg/detectors/zohocrm/zohocrm.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package zohocrm
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
-}
-
-type UnauthorizedResponseBody struct {
- Code string `json:"code"`
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = common.SaneHttpClient()
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(`\b(1000\.[a-f0-9]{32}\.[a-f0-9]{32})\b`)
-)
-
-// Keywords are used for efficiently pre-filtering chunks.
-// Use identifiers in the secret preferably, or the provider name.
-func (s Scanner) Keywords() []string {
- return []string{"1000."}
-}
-
-// FromData will find and optionally verify Zoho CRM secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
- uniqueMatches := make(map[string]struct{})
-
- for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- uniqueMatches[match[1]] = struct{}{}
- }
-
- for match := range uniqueMatches {
- result := detectors.Result{
- DetectorType: detectorspb.DetectorType_ZohoCRM,
- Raw: []byte(match),
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
-
- isVerified, verificationErr := verifyMatch(ctx, client, match)
- result.Verified = isVerified
- result.SetVerificationError(verificationErr, match)
- }
-
- results = append(results, result)
- }
-
- return
-}
-
-// Verifies the Zoho CRM API access token by making a GET request to the Zoho CRM API.
-func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
- endpoint := "https://www.zohoapis.com/crm/v7/Leads?fields=Email&per_page=1"
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody)
- if err != nil {
- return false, err
- }
- req.Header.Add("Authorization", fmt.Sprintf("Zoho-oauthtoken %s", token))
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- bodyBytes, err := io.ReadAll(res.Body)
- if err != nil {
- return false, fmt.Errorf("failed to read response body: %v", err)
- }
-
- var responseBody UnauthorizedResponseBody
- err = json.Unmarshal(bodyBytes, &responseBody)
- if err != nil {
- return false, fmt.Errorf("failed to parse JSON response: %v", err)
- }
-
- switch responseBody.Code {
- case "OAUTH_SCOPE_MISMATCH":
- return true, nil
- case "INVALID_TOKEN":
- return false, nil
- default:
- return false, fmt.Errorf("unexpected error code: %s", responseBody.Code)
- }
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ZohoCRM
-}
-
-func (s Scanner) Description() string {
- return "Zoho CRM is a platform for managing sales, marketing, and customer support. Zoho CRM API access tokens allow access to these services through their REST API."
-}
diff --git a/pkg/detectors/zohocrm/zohocrm_integration_test.go b/pkg/detectors/zohocrm/zohocrm_integration_test.go
deleted file mode 100644
index e9e960a1afdc..000000000000
--- a/pkg/detectors/zohocrm/zohocrm_integration_test.go
+++ /dev/null
@@ -1,167 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zohocrm
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-// TestZohocrm_FromChunk verifies the validity of a ZohoCRM access token
-// Note: The token validity test relies on an access token stored in the GCP secret manager.
-// Since Zoho CRM tokens expire after 60 minutes, this test will eventually fail once the token becomes invalid.
-// The official guide linked below can be followed in order to generate a new valid access token:
-// https://www.zoho.com/accounts/protocol/oauth/self-client/authorization-code-flow.html
-
-func TestZohocrm_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZOHOCRM")
- inactiveSecret := testSecrets.MustGetField("ZOHOCRM_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zohocrm secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZohoCRM,
- Verified: true,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zohocrm secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZohoCRM,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zohocrm secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZohoCRM,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zohocrm secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZohoCRM,
- Verified: false,
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Zohocrm.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Zohocrm.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zohocrm/zohocrm_test.go b/pkg/detectors/zohocrm/zohocrm_test.go
deleted file mode 100644
index b2b7829cb6ed..000000000000
--- a/pkg/detectors/zohocrm/zohocrm_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package zohocrm
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "1000.1fa6966eafbb115624baa4103269e50e.e57d155232227b4e41fa7dd2b88dd4d4"
- invalidPattern = "1000.24baa4103269e50e.41fa7dd2b88dd4d4"
-)
-
-func TestZohocrm_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "typical pattern - with keyword zoho crm",
- input: fmt.Sprintf("zoho crm token = '%s'", validPattern),
- want: []string{"1000.1fa6966eafbb115624baa4103269e50e.e57d155232227b4e41fa7dd2b88dd4d4"},
- },
- {
- name: "typical pattern - ignore duplicate",
- input: fmt.Sprintf("zoho crm token = '%s' | '%s'", validPattern, validPattern),
- want: []string{"1000.1fa6966eafbb115624baa4103269e50e.e57d155232227b4e41fa7dd2b88dd4d4"},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("zoho crm = '%s'", invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zonkafeedback/zonkafeedback.go b/pkg/detectors/zonkafeedback/zonkafeedback.go
deleted file mode 100644
index bee7d6ce54f8..000000000000
--- a/pkg/detectors/zonkafeedback/zonkafeedback.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package zonkafeedback
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct{}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- client = common.SaneHttpClient()
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zonka"}) + `\b([A-Za-z0-9]{36})\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{"zonka"}
-}
-
-// FromData will find and optionally verify ZonkaFeedback secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- matches := keyPat.FindAllStringSubmatch(dataStr, -1)
-
- for _, match := range matches {
- resMatch := strings.TrimSpace(match[1])
-
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ZonkaFeedback,
- Raw: []byte(resMatch),
- }
-
- if verify {
- isVerified, verificationErr := verifyZonkaFeedbackKey(ctx, client, resMatch)
- s1.Verified = isVerified
- s1.SetVerificationError(verificationErr, resMatch)
- }
-
- results = append(results, s1)
- }
-
- return results, nil
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ZonkaFeedback
-}
-
-func (s Scanner) Description() string {
- return "ZonkaFeedback is a platform for collecting and analyzing customer feedback. The API token can be used to access and manage feedback data."
-}
-
-func verifyZonkaFeedbackKey(ctx context.Context, client *http.Client, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet,
- "https://app-us1.zonkafeedback.com/responses?page=1&limit=25&startDate=2020-05-02&endDate=2020-05-09", http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Add("Z-API-TOKEN", key)
-
- resp, err := client.Do(req)
- if err != nil {
- return false, err
- }
-
- defer func() {
- _, _ = io.Copy(io.Discard, resp.Body)
- _ = resp.Body.Close()
- }()
-
- switch resp.StatusCode {
- case http.StatusOK:
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-}
diff --git a/pkg/detectors/zonkafeedback/zonkafeedback_integration_test.go b/pkg/detectors/zonkafeedback/zonkafeedback_integration_test.go
deleted file mode 100644
index 973aae60598d..000000000000
--- a/pkg/detectors/zonkafeedback/zonkafeedback_integration_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zonkafeedback
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/kylelemons/godebug/pretty"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZonkaFeedback_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZONKAFEEDBACK_TOKEN")
- inactiveSecret := testSecrets.MustGetField("ZONKAFEEDBACK_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zonkafeedback secret %s within", secret)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZonkaFeedback,
- Verified: true,
- },
- },
- wantErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zonkafeedback secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZonkaFeedback,
- Verified: false,
- },
- },
- wantErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := Scanner{}
- got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("ZonkaFeedback.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- got[i].Raw = nil
- }
- if diff := pretty.Compare(got, tt.want); diff != "" {
- t.Errorf("ZonkaFeedback.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zonkafeedback/zonkafeedback_test.go b/pkg/detectors/zonkafeedback/zonkafeedback_test.go
deleted file mode 100644
index 2fd3a68e96d2..000000000000
--- a/pkg/detectors/zonkafeedback/zonkafeedback_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package zonkafeedback
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validPattern = "WwpY1WKIBFnxr41nJLLk8nWnCpJ2hFz58Eoa"
- invalidPattern = "WwpY1WKIBFnxr41nJ?Lk8nWnCpJ2hFz58Eoa"
- keyword = "zonkafeedback"
-)
-
-func TestZonkaFeedback_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zonkafeedback",
- input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - ignore duplicate",
- input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
- want: []string{validPattern},
- },
- {
- name: "valid pattern - key out of prefix range",
- input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
- want: []string{},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/detectors/zulipchat/zulipchat.go b/pkg/detectors/zulipchat/zulipchat.go
deleted file mode 100644
index bdd5da1494c5..000000000000
--- a/pkg/detectors/zulipchat/zulipchat.go
+++ /dev/null
@@ -1,137 +0,0 @@
-package zulipchat
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
-
- regexp "github.com/wasilibs/go-re2"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-type Scanner struct {
- client *http.Client
- detectors.DefaultMultiPartCredentialProvider
-}
-
-// Ensure the Scanner satisfies the interface at compile time.
-var _ detectors.Detector = (*Scanner)(nil)
-
-var (
- defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
-
- // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
- keyPat = regexp.MustCompile(common.BuildRegex(common.AlphaNumPattern, "", 32))
- idPat = regexp.MustCompile(`\b([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\b`)
- domainPat = regexp.MustCompile(`(?i)\b([a-z0-9-]+\.zulip(?:chat)?\.com|chat\.zulip\.org)\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{"zulip"}
-}
-
-// FromData will find and optionally verify ZulipChat secrets in a given set of bytes.
-func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
- dataStr := string(data)
-
- keyMatches := make(map[string]struct{})
- for _, m := range keyPat.FindAllStringSubmatch(dataStr, -1) {
- keyMatches[m[1]] = struct{}{}
- }
- idMatches := make(map[string]struct{})
- for _, m := range idPat.FindAllStringSubmatch(dataStr, -1) {
- idMatches[m[1]] = struct{}{}
- }
- domainMatches := make(map[string]struct{})
- for _, m := range domainPat.FindAllStringSubmatch(dataStr, -1) {
- domainMatches[m[1]] = struct{}{}
- }
-
- for key := range keyMatches {
- for id := range idMatches {
- for domain := range domainMatches {
- s1 := detectors.Result{
- DetectorType: detectorspb.DetectorType_ZulipChat,
- Raw: []byte(key),
- RawV2: []byte(fmt.Sprintf("%s:%s:%s", key, id, domain)),
- ExtraData: map[string]string{
- "Domain": domain,
- "Id": id,
- },
- }
-
- if verify {
- client := s.client
- if client == nil {
- client = defaultClient
- }
- verified, verificationErr := verifyResult(ctx, client, domain, id, key)
- s1.Verified = verified
- s1.SetVerificationError(verificationErr)
- }
-
- results = append(results, s1)
- }
- }
- }
-
- return results, nil
-}
-
-func verifyResult(ctx context.Context, client *http.Client, domain, id, key string) (bool, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://%s/api/v1/users", domain), http.NoBody)
- if err != nil {
- return false, err
- }
-
- req.Header.Set("Accept", "application/json")
- req.Header.Set("Content-Type", "application/json")
- req.SetBasicAuth(id, key)
-
- res, err := client.Do(req)
- if err != nil {
- return false, err
- }
- defer func() {
- _, _ = io.Copy(io.Discard, res.Body)
- _ = res.Body.Close()
- }()
-
- switch res.StatusCode {
- case http.StatusOK:
- var users usersResponse
- if err := json.NewDecoder(res.Body).Decode(&users); err != nil {
- return false, nil
- }
- return true, nil
- case http.StatusUnauthorized:
- return false, nil
- default:
- return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
- }
-}
-
-type usersResponse struct {
- Result string `json:"result"`
- Members []member `json:"members"`
-}
-
-type member struct {
- FullName string `json:"full_name"`
- Email string `json:"email"`
-}
-
-func (s Scanner) Type() detectorspb.DetectorType {
- return detectorspb.DetectorType_ZulipChat
-}
-
-func (s Scanner) Description() string {
- return "ZulipChat is a group chat application used for team communication. ZulipChat API keys can be used to access and manage various functionalities of the chat service."
-}
diff --git a/pkg/detectors/zulipchat/zulipchat_integration_test.go b/pkg/detectors/zulipchat/zulipchat_integration_test.go
deleted file mode 100644
index 6d60ea910fdc..000000000000
--- a/pkg/detectors/zulipchat/zulipchat_integration_test.go
+++ /dev/null
@@ -1,167 +0,0 @@
-//go:build detectors
-// +build detectors
-
-package zulipchat
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestZulipChat_FromChunk(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
- if err != nil {
- t.Fatalf("could not get test secrets from GCP: %s", err)
- }
- secret := testSecrets.MustGetField("ZULIPCHAT")
- domain := testSecrets.MustGetField("ZULIPCHAT_DOMAINV2")
- id := testSecrets.MustGetField("ZULIPCHAT_ID")
- inactiveSecret := testSecrets.MustGetField("ZULIPCHAT_INACTIVE")
-
- type args struct {
- ctx context.Context
- data []byte
- verify bool
- }
- tests := []struct {
- name string
- s Scanner
- args args
- want []detectors.Result
- wantErr bool
- wantVerificationErr bool
- }{
- {
- name: "found, verified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zulipchat secret %s within zulipchat %s and zulipchat %s", secret, id, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZulipChat,
- Verified: true,
- ExtraData: map[string]string{"Domain": "secretscanner.zulipchat.com", "Id": "knightmoverchan@gmail.com"},
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, unverified",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zulipchat secret %s within zulipchat %s and zulipchat %s but not valid", inactiveSecret, id, domain)), // the secret would satisfy the regex but not pass validation
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZulipChat,
- Verified: false,
- ExtraData: map[string]string{"Domain": "secretscanner.zulipchat.com", "Id": "knightmoverchan@gmail.com"},
- },
- },
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "not found",
- s: Scanner{},
- args: args{
- ctx: context.Background(),
- data: []byte("You cannot find the secret within"),
- verify: true,
- },
- want: nil,
- wantErr: false,
- wantVerificationErr: false,
- },
- {
- name: "found, would be verified if not for timeout",
- s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zulipchat secret %s within zulipchat %s and zulipchat %s", secret, id, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZulipChat,
- Verified: false,
- ExtraData: map[string]string{"Domain": "secretscanner.zulipchat.com", "Id": "knightmoverchan@gmail.com"},
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- {
- name: "found, verified but unexpected api surface",
- s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
- args: args{
- ctx: context.Background(),
- data: []byte(fmt.Sprintf("You can find a zulipchat secret %s within zulipchat %s and zulipchat %s", secret, id, domain)),
- verify: true,
- },
- want: []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType_ZulipChat,
- Verified: false,
- ExtraData: map[string]string{"Domain": "secretscanner.zulipchat.com", "Id": "knightmoverchan@gmail.com"},
- },
- },
- wantErr: false,
- wantVerificationErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
- if (err != nil) != tt.wantErr {
- t.Errorf("Zulipchat.FromData() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- for i := range got {
- if len(got[i].Raw) == 0 {
- t.Fatalf("no raw secret present: \n %+v", got[i])
- }
- if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
- t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
- }
- }
- ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
- if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
- t.Errorf("Zulipchat.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
- }
- })
- }
-}
-
-func BenchmarkFromData(benchmark *testing.B) {
- ctx := context.Background()
- s := Scanner{}
- for name, data := range detectors.MustGetBenchmarkData() {
- benchmark.Run(name, func(b *testing.B) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- _, err := s.FromData(ctx, false, data)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- }
-}
diff --git a/pkg/detectors/zulipchat/zulipchat_test.go b/pkg/detectors/zulipchat/zulipchat_test.go
deleted file mode 100644
index 05743e337473..000000000000
--- a/pkg/detectors/zulipchat/zulipchat_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package zulipchat
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/google/go-cmp/cmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
-)
-
-var (
- validKey = "zwoltpBbfT4QjuNst0Gms4TXnK4rMMKX"
- invalidKey = "z?oltpBbfT4QjuNst0Gms4TXnK4rMMKX"
- validId = "aE5f.L1CIrn4WwIq_YB1DsB-E11p_8azu7x@aujstMgjEY5.com"
- invalidId = "aE5f.L1?Irn4WwIq_YB1DsB-E11p_8azu7x@aujstMgjEY5.com"
- validDomain = "dwj7s0gr-uedt0sg-ll.zulipchat.com"
- invalidDomain = "d?j7s0gr-uedt0sg-ll.zulipchat.com"
- keyword = "zulipchat"
-)
-
-func TestZulipChat_Pattern(t *testing.T) {
- d := Scanner{}
- ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
- tests := []struct {
- name string
- input string
- want []string
- }{
- {
- name: "valid pattern - with keyword zulipchat",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validId, keyword, validDomain),
- want: []string{validKey + ":" + validId + ":" + validDomain},
- },
- {
- name: "invalid pattern",
- input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidId, keyword, invalidDomain),
- want: []string{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
- if len(matchedDetectors) == 0 {
- t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
- return
- }
-
- results, err := d.FromData(context.Background(), false, []byte(test.input))
- if err != nil {
- t.Errorf("error = %v", err)
- return
- }
-
- if len(results) != len(test.want) {
- if len(results) == 0 {
- t.Errorf("did not receive result")
- } else {
- t.Errorf("expected %d results, only received %d", len(test.want), len(results))
- }
- return
- }
-
- actual := make(map[string]struct{}, len(results))
- for _, r := range results {
- if len(r.RawV2) > 0 {
- actual[string(r.RawV2)] = struct{}{}
- } else {
- actual[string(r.Raw)] = struct{}{}
- }
- }
- expected := make(map[string]struct{}, len(test.want))
- for _, v := range test.want {
- expected[v] = struct{}{}
- }
-
- if diff := cmp.Diff(expected, actual); diff != "" {
- t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
- }
- })
- }
-}
diff --git a/pkg/engine/ahocorasick/ahocorasickcore.go b/pkg/engine/ahocorasick/ahocorasickcore.go
deleted file mode 100644
index c6f97da1476c..000000000000
--- a/pkg/engine/ahocorasick/ahocorasickcore.go
+++ /dev/null
@@ -1,304 +0,0 @@
-package ahocorasick
-
-import (
- "bytes"
- "strings"
-
- ahocorasick "github.com/BobuSumisu/aho-corasick"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/custom_detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-// DetectorKey is used to identify a detector in the keywordsToDetectors map.
-// Multiple detectors can have the same detector type but different versions.
-// This allows us to identify a detector by its type and version. An
-// additional (optional) field is provided to disambiguate multiple custom
-// detectors. This type is exported even though none of its fields are so
-// that the AhoCorasickCore can populate passed-in maps keyed on this type
-// without exposing any of its internals to consumers.
-type DetectorKey struct {
- detectorType detectorspb.DetectorType
- version int
- customDetectorName string
-}
-
-func (k DetectorKey) Loggable() map[string]any {
- res := map[string]any{"type": k.detectorType.String()}
- if k.version > 0 {
- res["version"] = k.version
- }
- if k.customDetectorName != "" {
- res["name"] = k.customDetectorName
- }
- return res
-}
-
-// Type returns the detector type of the key.
-func (k DetectorKey) Type() detectorspb.DetectorType { return k.detectorType }
-
-// spanCalculator is an interface that defines a method for calculating a match span
-// in the chunk data. This allows for different strategies to be used without changing the core logic.
-type spanCalculator interface {
- calculateSpan(params spanCalculationParams) matchSpan
-}
-
-// spanCalculationParams provides the necessary context for calculating match spans,
-// including the keyword index in the chunk, the chunk data itself, and the detector being used.
-type spanCalculationParams struct {
- keywordIdx int64 // Index of the keyword in the chunk data
- chunkData []byte
- detector detectors.Detector
-}
-
-// EntireChunkSpanCalculator is a strategy that calculates the match span to use the entire chunk data.
-// This is used when we want to match against the full length of the provided chunk.
-type EntireChunkSpanCalculator struct{}
-
-// calculateSpan returns the match span as the length of the chunk data,
-// effectively using the entire chunk for matching.
-func (e *EntireChunkSpanCalculator) calculateSpan(params spanCalculationParams) matchSpan {
- return matchSpan{startOffset: 0, endOffset: int64(len(params.chunkData))}
-}
-
-// adjustableSpanCalculator is a strategy that calculates match spans. It uses a default offset magnitude
-// or values provided by specific detectors to adjust the start and end indices of the span, allowing
-// for more granular control over the match.
-type adjustableSpanCalculator struct{ offsetMagnitude int64 }
-
-// newAdjustableSpanCalculator creates a new instance of adjustableSpanCalculator with the
-// specified offset magnitude.
-func newAdjustableSpanCalculator(offsetRadius int64) *adjustableSpanCalculator {
- return &adjustableSpanCalculator{offsetMagnitude: offsetRadius}
-}
-
-// calculateSpan computes the match span based on the keyword index and the offset magnitude.
-// If the detector provides an override value, it uses that instead of the default offset magnitude to
-// calculate the maximum size of the span.
-// The start index of the span is also adjusted if the detector provides a start offset.
-func (m *adjustableSpanCalculator) calculateSpan(params spanCalculationParams) matchSpan {
- keywordIdx := params.keywordIdx
-
- maxSize := keywordIdx + m.offsetMagnitude
- startOffset := keywordIdx - m.offsetMagnitude
-
- // Check if the detector implements each interface and update values accordingly.
- // This CAN'T be done in a switch statement because a detector can implement multiple interfaces.
- if provider, ok := params.detector.(detectors.MultiPartCredentialProvider); ok {
- maxSize = provider.MaxCredentialSpan() + keywordIdx
- startOffset = keywordIdx - provider.MaxCredentialSpan()
- }
- if provider, ok := params.detector.(detectors.MaxSecretSizeProvider); ok {
- maxSize = provider.MaxSecretSize() + keywordIdx
- }
- if provider, ok := params.detector.(detectors.StartOffsetProvider); ok {
- startOffset = keywordIdx - provider.StartOffset()
- }
-
- startIdx := max(startOffset, 0)
- endIdx := min(maxSize, int64(len(params.chunkData)))
-
- // Ensure the start index is not greater than the end index to prevent invalid spans.
- // In rare cases where the calculated start index exceeds the end index (possibly due to
- // detector-provided offsets), we reset the start index to 0 to maintain a valid span range
- // and avoid runtime panics. This is a temporary fix until the root cause is identified.
- if startIdx >= endIdx {
- startIdx = 0
- }
-
- return matchSpan{startOffset: startIdx, endOffset: endIdx}
-}
-
-// CoreOption is a functional option type for configuring an AhoCorasickCore instance.
-type CoreOption func(*Core)
-
-// WithSpanCalculator sets the span calculator for AhoCorasickCore.
-func WithSpanCalculator(spanCalculator spanCalculator) CoreOption {
- return func(ac *Core) { ac.spanCalculator = spanCalculator }
-}
-
-// Core encapsulates the operations and data structures used for keyword matching via the
-// Aho-Corasick algorithm. It is responsible for constructing and managing the trie for efficient
-// substring searches, as well as mapping keywords to their associated detectors for rapid lookups.
-type Core struct {
- // prefilter is a ahocorasick struct used for doing efficient string
- // matching given a set of words. (keywords from the rules in the config)
- prefilter ahocorasick.Trie
- // Maps for efficient lookups during detection.
- // (This implementation maps in two layers: from keywords to detector
- // type and then again from detector type to detector. We could
- // go straight from keywords to detectors but doing it this way makes
- // some consuming code a little cleaner.)
- keywordsToDetectors map[string][]DetectorKey
- detectorsByKey map[DetectorKey]detectors.Detector
- spanCalculator spanCalculator // Strategy for calculating match spans
-}
-
-// NewAhoCorasickCore allocates and initializes a new instance of AhoCorasickCore. It uses the
-// provided detector slice to create a map from keywords to detectors and build the Aho-Corasick
-// prefilter trie.
-func NewAhoCorasickCore(allDetectors []detectors.Detector, opts ...CoreOption) *Core {
- keywordsToDetectors := make(map[string][]DetectorKey)
- detectorsByKey := make(map[DetectorKey]detectors.Detector, len(allDetectors))
- var keywords []string
- for _, d := range allDetectors {
- key := CreateDetectorKey(d)
- detectorsByKey[key] = d
- for _, kw := range d.Keywords() {
- kwLower := strings.ToLower(kw)
- keywords = append(keywords, kwLower)
- keywordsToDetectors[kwLower] = append(keywordsToDetectors[kwLower], key)
- }
- }
-
- const defaultOffsetRadius int64 = 512
- core := &Core{
- keywordsToDetectors: keywordsToDetectors,
- detectorsByKey: detectorsByKey,
- prefilter: *ahocorasick.NewTrieBuilder().AddStrings(keywords).Build(),
- spanCalculator: newAdjustableSpanCalculator(defaultOffsetRadius), // Default span calculator
- }
-
- for _, opt := range opts {
- opt(core)
- }
-
- return core
-}
-
-// DetectorMatch represents a detected pattern's metadata in a data chunk.
-// It encapsulates the key identifying a specific detector, the detector instance itself,
-// the start and end offsets of the matched keyword in the chunk, and the matched portions of the chunk data.
-type DetectorMatch struct {
- Key DetectorKey
- detectors.Detector
- matchSpans []matchSpan
-
- // matches is a slice of byte slices, each representing a matched portion of the chunk data.
- matches [][]byte
-}
-
-// MatchSpan represents a single occurrence of a matched keyword in the chunk.
-// It contains the start and end byte offsets of the matched keyword within the chunk.
-type matchSpan struct {
- startOffset int64
- endOffset int64
-}
-
-// addMatchSpan adds a match span to the DetectorMatch instance.
-func (d *DetectorMatch) addMatchSpan(spans ...matchSpan) {
- d.matchSpans = append(d.matchSpans, spans...)
-}
-
-// mergeMatches merges overlapping or adjacent matchSpans into a single matchSpan.
-// It updates the matchSpans field with the merged spans.
-func (d *DetectorMatch) mergeMatches() {
- if len(d.matchSpans) <= 1 {
- return
- }
-
- merged := make([]matchSpan, 0, len(d.matchSpans))
- current := d.matchSpans[0]
-
- for i := 1; i < len(d.matchSpans); i++ {
- if d.matchSpans[i].startOffset <= current.endOffset {
- if d.matchSpans[i].endOffset > current.endOffset {
- current.endOffset = d.matchSpans[i].endOffset
- }
- continue
- }
- merged = append(merged, current)
- current = d.matchSpans[i]
- }
-
- merged = append(merged, current)
- d.matchSpans = merged
-}
-
-// extractMatches extracts the matched portions from the chunk data and stores them in the matches field.
-func (d *DetectorMatch) extractMatches(chunkData []byte) {
- d.matches = make([][]byte, len(d.matchSpans))
- for i, m := range d.matchSpans {
- d.matches[i] = chunkData[m.startOffset:m.endOffset]
- }
-}
-
-// Matches returns a slice of byte slices, each representing a matched portion of the chunk data.
-func (d *DetectorMatch) Matches() [][]byte { return d.matches }
-
-// FindDetectorMatches finds the matching detectors for a given chunk of data using the Aho-Corasick algorithm.
-// It returns a slice of DetectorMatch instances, each containing the detector key, detector,
-// a slice of matchSpans, and the corresponding matched portions of the chunk data.
-//
-// Each matchSpan represents a position in the chunk data where a keyword was found,
-// along with the corresponding span (start and end positions).
-// The span is determined based on the configured spanCalculator strategy.
-// Adjacent or overlapping matches are merged to avoid duplicating or overlapping the matched
-// portions of the chunk data.
-//
-// The matches field contains the actual byte slices of the matched portions from the chunk data.
-func (ac *Core) FindDetectorMatches(chunkData []byte) []*DetectorMatch {
- matches := ac.prefilter.Match(bytes.ToLower(chunkData))
-
- matchCount := len(matches)
- if matchCount == 0 {
- return nil
- }
-
- detectorMatches := make(map[DetectorKey]*DetectorMatch)
-
- for _, m := range matches {
- for _, k := range ac.keywordsToDetectors[m.MatchString()] {
- if _, exists := detectorMatches[k]; !exists {
- detector := ac.detectorsByKey[k]
- detectorMatches[k] = &DetectorMatch{
- Key: k,
- Detector: detector,
- matchSpans: make([]matchSpan, 0),
- }
- }
-
- detectorMatch := detectorMatches[k]
- startIdx := m.Pos()
- span := ac.spanCalculator.calculateSpan(
- spanCalculationParams{
- keywordIdx: startIdx,
- chunkData: chunkData,
- detector: detectorMatch.Detector,
- },
- )
- detectorMatch.addMatchSpan(span)
- }
- }
-
- uniqueDetectors := make([]*DetectorMatch, 0, len(detectorMatches))
- for _, detectorMatch := range detectorMatches {
- // Merge overlapping or adjacent match spans.
- detectorMatch.mergeMatches()
- detectorMatch.extractMatches(chunkData)
-
- uniqueDetectors = append(uniqueDetectors, detectorMatch)
- }
-
- return uniqueDetectors
-}
-
-// CreateDetectorKey creates a unique key for each detector from its type, version, and, for
-// custom regex detectors, its name.
-func CreateDetectorKey(d detectors.Detector) DetectorKey {
- detectorType := d.Type()
- var version int
- if v, ok := d.(detectors.Versioner); ok {
- version = v.Version()
- }
- var customDetectorName string
- if r, ok := d.(*custom_detectors.CustomRegexWebhook); ok {
- customDetectorName = r.GetName()
- }
- return DetectorKey{detectorType: detectorType, version: version, customDetectorName: customDetectorName}
-}
-
-func (ac *Core) KeywordsToDetectors() map[string][]DetectorKey {
- return ac.keywordsToDetectors
-}
diff --git a/pkg/engine/ahocorasick/ahocorasickcore_test.go b/pkg/engine/ahocorasick/ahocorasickcore_test.go
deleted file mode 100644
index c33954a7dd3c..000000000000
--- a/pkg/engine/ahocorasick/ahocorasickcore_test.go
+++ /dev/null
@@ -1,436 +0,0 @@
-package ahocorasick
-
-import (
- "context"
- "testing"
-
- "github.com/stretchr/testify/assert"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/custom_detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-const TestDetectorType = -1
-
-type testDetectorV1 struct {
-}
-
-func (testDetectorV1) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {
- return make([]detectors.Result, 0), nil
-}
-
-func (testDetectorV1) Keywords() []string { return []string{"a", "b"} }
-
-func (testDetectorV1) Type() detectorspb.DetectorType {
- return TestDetectorType
-}
-
-func (testDetectorV1) Version() int { return 1 }
-
-func (testDetectorV1) Description() string { return "" }
-
-type testDetectorV2 struct {
-}
-
-func (testDetectorV2) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {
- return make([]detectors.Result, 0), nil
-}
-
-func (testDetectorV2) Keywords() []string {
- return []string{"a"}
-}
-
-func (testDetectorV2) Type() detectorspb.DetectorType {
- return TestDetectorType
-}
-
-func (testDetectorV2) Version() int { return 2 }
-
-func (testDetectorV2) Description() string { return "" }
-
-type testDetectorV3 struct {
-}
-
-func (testDetectorV3) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {
- return make([]detectors.Result, 0), nil
-}
-
-func (testDetectorV3) Keywords() []string {
- return []string{"truffle"}
-}
-
-func (testDetectorV3) Type() detectorspb.DetectorType {
- return TestDetectorType
-}
-
-func (testDetectorV3) Version() int { return 1 }
-
-func (testDetectorV3) Description() string { return "" }
-
-var _ detectors.Detector = (*testDetectorV4)(nil)
-var _ detectors.MultiPartCredentialProvider = (*testDetectorV4)(nil)
-var _ detectors.StartOffsetProvider = (*testDetectorV4)(nil)
-
-type testDetectorV4 struct{}
-
-func (testDetectorV4) FromData(context.Context, bool, []byte) ([]detectors.Result, error) {
- return make([]detectors.Result, 0), nil
-}
-
-func (testDetectorV4) Keywords() []string { return []string{"password"} }
-
-func (testDetectorV4) Type() detectorspb.DetectorType { return TestDetectorType }
-
-func (testDetectorV4) Version() int { return 1 }
-
-func (testDetectorV4) Description() string { return "" }
-
-func (testDetectorV4) MaxCredentialSpan() int64 { return 15 }
-
-func (testDetectorV4) StartOffset() int64 { return 5 }
-
-var _ detectors.Detector = (*testDetectorV5)(nil)
-var _ detectors.MaxSecretSizeProvider = (*testDetectorV5)(nil)
-var _ detectors.StartOffsetProvider = (*testDetectorV5)(nil)
-
-type testDetectorV5 struct{}
-
-func (testDetectorV5) FromData(context.Context, bool, []byte) ([]detectors.Result, error) {
- return make([]detectors.Result, 0), nil
-}
-
-func (testDetectorV5) Keywords() []string { return []string{"password"} }
-
-func (testDetectorV5) Type() detectorspb.DetectorType { return TestDetectorType }
-
-func (testDetectorV5) Version() int { return 1 }
-
-func (testDetectorV5) Description() string { return "" }
-
-func (testDetectorV5) MaxSecretSize() int64 { return 10 }
-
-func (testDetectorV5) StartOffset() int64 { return 3 }
-
-var _ detectors.Detector = (*testDetectorV6)(nil)
-var _ detectors.Detector = (*testDetectorV6)(nil)
-var _ detectors.StartOffsetProvider = (*testDetectorV6)(nil)
-
-type testDetectorV6 struct{}
-
-func (testDetectorV6) FromData(context.Context, bool, []byte) ([]detectors.Result, error) {
- return make([]detectors.Result, 0), nil
-}
-
-func (testDetectorV6) Keywords() []string { return []string{"password"} }
-
-func (testDetectorV6) Type() detectorspb.DetectorType { return TestDetectorType }
-
-func (testDetectorV6) Version() int { return 1 }
-
-func (testDetectorV6) Description() string { return "" }
-
-func (testDetectorV6) StartOffset() int64 { return 1 }
-
-var _ detectors.Detector = (*testDetectorV1)(nil)
-var _ detectors.Detector = (*testDetectorV2)(nil)
-var _ detectors.Versioner = (*testDetectorV1)(nil)
-var _ detectors.Versioner = (*testDetectorV2)(nil)
-var _ detectors.Versioner = (*testDetectorV3)(nil)
-
-func TestAhoCorasickCore_MultipleCustomDetectorsMatchable(t *testing.T) {
- customDetector1, err := custom_detectors.NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{
- Name: "custom detector 1",
- Keywords: []string{"a"},
- Regex: map[string]string{"": ""},
- })
- assert.Nil(t, err)
-
- customDetector2, err := custom_detectors.NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{
- Name: "custom detector 2",
- Keywords: []string{"a"},
- Regex: map[string]string{"": ""},
- })
- assert.Nil(t, err)
-
- allDetectors := []detectors.Detector{customDetector1, customDetector2}
-
- ac := NewAhoCorasickCore(allDetectors)
-
- dts := ac.FindDetectorMatches([]byte("a"))
- matchingDetectors := make([]detectors.Detector, 0, 2)
- for _, d := range dts {
- matchingDetectors = append(matchingDetectors, d.Detector)
- }
- assert.ElementsMatch(t, allDetectors, matchingDetectors)
-}
-
-func TestAhoCorasickCore_MultipleDetectorVersionsMatchable(t *testing.T) {
- v1 := testDetectorV1{}
- v2 := testDetectorV2{}
- allDetectors := []detectors.Detector{v1, v2}
-
- ac := NewAhoCorasickCore(allDetectors)
-
- dts := ac.FindDetectorMatches([]byte("a"))
- matchingDetectors := make([]detectors.Detector, 0, 2)
- for _, d := range dts {
- matchingDetectors = append(matchingDetectors, d.Detector)
- }
- assert.ElementsMatch(t, allDetectors, matchingDetectors)
-}
-
-func TestAhoCorasickCore_NoDuplicateDetectorsMatched(t *testing.T) {
- d := testDetectorV1{}
- allDetectors := []detectors.Detector{d}
-
- ac := NewAhoCorasickCore(allDetectors)
-
- dts := ac.FindDetectorMatches([]byte("a a b b"))
- matchingDetectors := make([]detectors.Detector, 0, 2)
- for _, d := range dts {
- matchingDetectors = append(matchingDetectors, d.Detector)
- }
- assert.ElementsMatch(t, allDetectors, matchingDetectors)
-}
-
-func TestFindDetectorMatches(t *testing.T) {
- testCases := []struct {
- name string
- opts []CoreOption
- detectors []detectors.Detector
- sampleData string
- expectedResult map[DetectorKey][][]int64
- }{
-
- {
- name: "single matchSpan",
- detectors: []detectors.Detector{
- testDetectorV3{},
- },
- sampleData: "This is a sample data containing keyword truffle",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV3{}): {{0, 48}},
- },
- },
- {
- name: "Multiple matches overlapping",
- detectors: []detectors.Detector{
- testDetectorV1{},
- },
- sampleData: "This is a sample data containing keyword a",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV1{}): {{0, 42}},
- },
- },
- {
- name: "Multiple matches",
- detectors: []detectors.Detector{
- testDetectorV2{},
- },
- sampleData: `This is the first occurrence of the letter a.
- Lorem ipsum dolor sit met, consectetur dipiscing elit. Sed uctor,
- mgn bibendum bibendum, ugue ugue tincidunt ugue,
- eget ultricies ugue ugue id ugue. Meens liquet libero
- c libero molestie, nec mlesud ugue ugue eget. Donec
- sed ugue. Sed euismod, ugue sit met liqum lcini,
- ugue ugue tincidunt ugue, eget ultricies ugue ugue id
- ugue. Meens liquet libero c libero molestie, nec
- mlesud ugue ugue eget. Donec sed ugue. Sed euismod,
- ugue sit met liqum lcini, ugue ugue tincidunt ugue,
- eget ultricies ugue ugue id ugue. Meens liquet libero
- c libero molestie, nec mlesud ugue ugue eget. This is the second occurrence of the letter a.`,
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV2{}): {{0, 856}},
- },
- },
- {
- name: "single matchSpan; entireSpanChunkCalculator",
- opts: []CoreOption{WithSpanCalculator(&EntireChunkSpanCalculator{})},
- detectors: []detectors.Detector{
- testDetectorV3{},
- },
- sampleData: "This is a sample data containing keyword truffle",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV3{}): {{0, 48}},
- },
- },
- {
- name: "Multiple matches overlapping; entireSpanChunkCalculator",
- opts: []CoreOption{WithSpanCalculator(&EntireChunkSpanCalculator{})},
- detectors: []detectors.Detector{
- testDetectorV1{},
- },
- sampleData: "This is a sample data containing keyword a",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV1{}): {{0, 42}},
- },
- },
- {
- name: "Multiple matches; entireSpanChunkCalculator",
- opts: []CoreOption{WithSpanCalculator(&EntireChunkSpanCalculator{})},
- detectors: []detectors.Detector{
- testDetectorV2{},
- },
- sampleData: `This is the first occurrence of the letter a.
- Lorem ipsum dolor sit met, consectetur dipiscing elit. Sed uctor,
- mgn bibendum bibendum, ugue ugue tincidunt ugue,
- eget ultricies ugue ugue id ugue. Meens liquet libero
- c libero molestie, nec mlesud ugue ugue eget. Donec
- sed ugue. Sed euismod, ugue sit met liqum lcini,
- ugue ugue tincidunt ugue, eget ultricies ugue ugue id
- ugue. Meens liquet libero c libero molestie, nec
- mlesud ugue ugue eget. Donec sed ugue. Sed euismod,
- ugue sit met liqum lcini, ugue ugue tincidunt ugue,
- eget ultricies ugue ugue id ugue. Meens liquet libero
- c libero molestie, nec mlesud ugue ugue eget. This is the second occurrence of the letter a.`,
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV2{}): {{0, 856}},
- },
- },
- {
- name: "keyword in the middle of the credential; MultiPartCredentialProvider, StartOffsetProvider",
- detectors: []detectors.Detector{
- testDetectorV4{},
- },
- sampleData: "This is a password in the middle of some data",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV4{}): {{5, 25}},
- },
- },
- {
- name: "keyword at the end of the credential; MultiPartCredentialProvider, StartOffsetProvider",
- detectors: []detectors.Detector{
- testDetectorV4{},
- },
- sampleData: "This data ends with a password",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV4{}): {{17, 30}},
- },
- },
- {
- name: "keyword near the start of the data; MultiPartCredentialProvider, StartOffsetProvider",
- detectors: []detectors.Detector{
- testDetectorV4{},
- },
- sampleData: "a password at the start",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV4{}): {{0, 17}},
- },
- },
- {
- name: "keyword in the middle of the credential; MaxSecretSizeProvider, StartOffsetProvider",
- detectors: []detectors.Detector{
- testDetectorV5{},
- },
- sampleData: "This is a password in the middle of some data",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV5{}): {{7, 20}},
- },
- },
- {
- name: "keyword at the end of the credential; MaxSecretSizeProvider, StartOffsetProvider",
- detectors: []detectors.Detector{
- testDetectorV5{},
- },
- sampleData: "This data ends with a password",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV5{}): {{19, 30}},
- },
- },
- {
- name: "keyword near the start of the data; MaxSecretSizeProvider, StartOffsetProvider",
- detectors: []detectors.Detector{
- testDetectorV5{},
- },
- sampleData: "a password at the start",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV5{}): {{0, 12}},
- },
- },
- {
- name: "keyword in the middle of the credential; StartOffsetProvider",
- detectors: []detectors.Detector{
- testDetectorV6{},
- },
- sampleData: "This is a password in the middle of some data",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV6{}): {{9, 45}},
- },
- },
- {
- name: "keyword at the end of the credential; StartOffsetProvider",
- detectors: []detectors.Detector{
- testDetectorV6{},
- },
- sampleData: "This data ends with a password",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV6{}): {{21, 30}},
- },
- },
- {
- name: "keyword near the start of the data; StartOffsetProvider",
- detectors: []detectors.Detector{
- testDetectorV6{},
- },
- sampleData: "a password at the start",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV6{}): {{1, 23}},
- },
- },
- {
- name: "multiple keyword in the middle of the credential; StartOffsetProvider",
- detectors: []detectors.Detector{
- testDetectorV6{},
- },
- sampleData: "This is a password in the middle of some data, and another password at the end!",
- expectedResult: map[DetectorKey][][]int64{
- CreateDetectorKey(testDetectorV6{}): {{9, 79}},
- },
- },
- {
- name: "No matches",
- detectors: []detectors.Detector{
- testDetectorV1{},
- testDetectorV2{},
- },
- sampleData: "xxy yzz lnnope",
- expectedResult: map[DetectorKey][][]int64{},
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- ac := NewAhoCorasickCore(tc.detectors, tc.opts...)
- detectorMatches := ac.FindDetectorMatches([]byte(tc.sampleData))
-
- // Verify that all matching detectors and their matches are returned.
- for _, detectorMatch := range detectorMatches {
- assert.Contains(t, tc.expectedResult, detectorMatch.Key, "Expected detector key to be present")
-
- expectedMatches := tc.expectedResult[detectorMatch.Key]
- actualMatches := make([][]int64, len(detectorMatch.matchSpans))
- for i, match := range detectorMatch.matchSpans {
- actualMatches[i] = []int64{match.startOffset, match.endOffset}
- }
-
- assert.ElementsMatch(t, expectedMatches, actualMatches, "Expected matches to be returned for the detector")
- }
-
- // Verify that all expected matches are returned for each detector.
- for key, expectedMatches := range tc.expectedResult {
- var actualMatches [][]int64
- for _, detectorMatch := range detectorMatches {
- if detectorMatch.Key == key {
- for _, match := range detectorMatch.matchSpans {
- actualMatches = append(actualMatches, []int64{match.startOffset, match.endOffset})
- }
- }
- }
- assert.ElementsMatch(t, expectedMatches, actualMatches, "Expected all matches to be returned for the detector")
- }
- })
- }
-}
diff --git a/pkg/engine/circleci.go b/pkg/engine/circleci.go
deleted file mode 100644
index c2ca134e51cb..000000000000
--- a/pkg/engine/circleci.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package engine
-
-import (
- "runtime"
-
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/circleci"
-)
-
-// ScanCircleCI scans CircleCI logs.
-func (e *Engine) ScanCircleCI(ctx context.Context, token string) (sources.JobProgressRef, error) {
- connection := &sourcespb.CircleCI{
- Credential: &sourcespb.CircleCI_Token{
- Token: token,
- },
- }
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal Circle CI connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - Circle CI"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, circleci.SourceType)
-
- circleSource := &circleci.Source{}
- if err := circleSource.Init(ctx, "trufflehog - Circle CI", jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
- return sources.JobProgressRef{}, err
- }
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, circleSource)
-}
diff --git a/pkg/engine/defaults/defaults.go b/pkg/engine/defaults/defaults.go
deleted file mode 100644
index 0b272af4408e..000000000000
--- a/pkg/engine/defaults/defaults.go
+++ /dev/null
@@ -1,1776 +0,0 @@
-package defaults
-
-import (
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/abuseipdb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/abyssale"
- accuweatherv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/accuweather/v1"
- accuweatherv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/accuweather/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/adafruitio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/adzuna"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aeroworkflow"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/agora"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aha"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airbrakeprojectkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airbrakeuserkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airship"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airtableoauth"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airtablepersonalaccesstoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airvisual"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aiven"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/alchemy"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/alegra"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aletheiaapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/algoliaadminkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/alibaba"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/alienvault"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/allsports"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/amadeus"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ambee"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/amplitudeapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/anthropic"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/anypoint"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apacta"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/api2cart"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apideck"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apiflash"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apifonica"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apify"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apilayer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apimatic"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apimetrics"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apitemplate"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/appcues"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/appfollow"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/appointedd"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/appoptics"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/appsynergy"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apptivo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/artifactory"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/artsy"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/asanaoauth"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/asanapersonalaccesstoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/assemblyai"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atera"
- atlassianv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atlassian/v1"
- atlassianv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atlassian/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/audd"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/auth0managementapitoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/auth0oauth"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/autodesk"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/autoklose"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/autopilot"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/avazapersonalaccesstoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aviationstack"
- aws_access_keys "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aws/access_keys"
- aws_session_keys "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aws/session_keys"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/axonaut"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aylien"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ayrshare"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_batch"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_cosmosdb"
- azure_entra_refreshtoken "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/refreshtoken"
- azure_entra_serviceprincipal_v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal/v1"
- azure_entra_serviceprincipal_v2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_openai"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_storage"
- azurerepositorykey "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azureapimanagement/repositorykey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azureapimanagementsubscriptionkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azureappconfigconnectionstring"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azurecontainerregistry"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azuredevopspersonalaccesstoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azuredirectmanagementkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azuresastoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azuresearchadminkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azuresearchquerykey"
- bannerbearv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bannerbear/v1"
- bannerbearv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bannerbear/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/baremetrics"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/beamer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/beebole"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/besttime"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/betterstack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/billomat"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bingsubscriptionkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitbar"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitbucketapppassword"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitcoinaverage"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitfinex"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitlyaccesstoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitmex"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/blazemeter"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/blitapp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/blogger"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bombbomb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/boostnote"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/borgbase"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/box"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/boxoauth"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/braintreepayments"
- brandfetchv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/brandfetch/v1"
- brandfetchv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/brandfetch/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/browserstack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/browshot"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bscscan"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/buddyns"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/budibase"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bugherd"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bugsnag"
- buildKitev1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/buildkite/v1"
- buildKitev2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/buildkite/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bulbul"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bulksms"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/buttercms"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/caflou"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/calendarific"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/calendlyapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/calorieninja"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/campayn"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cannyio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/capsulecrm"
- captainDataV1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/captaindata/v1"
- captainDataV2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/captaindata/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/carboninterface"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cashboard"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/caspio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/censys"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/centralstationcrm"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cexio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/chartmogul"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/chatbot"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/chatfule"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/checio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/checklyhq"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/checkout"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/checkvist"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cicero"
- circleciV1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/circleci/v1"
- circleciV2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/circleci/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clarifai"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clearbit"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clickhelp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clicksendsms"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clickuppersonaltoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cliengo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clientary"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clinchpad"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clockify"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clockworksms"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/closecrm"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudconvert"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudelements"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudflareapitoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudflarecakey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudflareglobalapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudimage"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudmersive"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudplan"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudsmith"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloverly"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloze"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clustdoc"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coda"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/codacy"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/codeclimate"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/codemagic"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/codequiry"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coinapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coinbase"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coinlayer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coinlib"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/collect2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/column"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/commercejs"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/commodities"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/companyhub"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/confluent"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/contentfulpersonalaccesstoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/conversiontools"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/convertapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/convertkit"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/convier"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/copper"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/couchbase"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/countrylayer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/courier"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coveralls"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/craftmypdf"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/crowdin"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cryptocompare"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/currencycloud"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/currencyfreaks"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/currencylayer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/currencyscoop"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/currentsapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/customerguru"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/customerio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/d7network"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dandelion"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dareboost"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/databox"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/databrickstoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/datadogtoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/datagov"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/deepai"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/deepgram"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/deepseek"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/delighted"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/demio"
- denodeploy "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/deno"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/deputy"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/detectify"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/detectlanguage"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dfuse"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/diffbot"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/diggernaut"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/digitaloceantoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/digitaloceanv2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/discordbottoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/discordwebhook"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/disqus"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ditto"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dnscheck"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/docker"
- dockerhubv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dockerhub/v1"
- dockerhubv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dockerhub/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/docparser"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/documo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/docusign"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/doppler"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dotdigital"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dovico"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dronahq"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/droneci"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dropbox"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/duply"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dwolla"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dynalist"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dyspatch"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/eagleeyenetworks"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/easyinsight"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ecostruxureit"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/edamam"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/edenai"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/eightxeight"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/elasticemail"
- elevenlabsv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/elevenlabs/v1"
- elevenlabsv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/elevenlabs/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/enablex"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/endorlabs"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/enigma"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/envoyapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/eraser"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/etherscan"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ethplorer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/eventbrite"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/everhour"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/exchangerateapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/exchangeratesapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/exportsdk"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/extractorapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/facebookoauth"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/faceplusplus"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fastforex"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fastlypersonaltoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/feedier"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fetchrss"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fibery"
- figmapersonalaccesstokenv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/figmapersonalaccesstoken/v1"
- figmapersonalaccesstokenv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/figmapersonalaccesstoken/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fileio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/finage"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/financialmodelingprep"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/findl"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/finnhub"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fixerio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flatio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fleetbase"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flexport"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flickr"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flightapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flightlabs"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flightstats"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/float"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flowflu"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flutterwave"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flyio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fmfw"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/formbucket"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/formcraft"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/formio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/formsite"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/foursquare"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/frameio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/freshbooks"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/freshdesk"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/front"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ftp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fulcrum"
- fullstoryv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fullstory/v1"
- fullstoryv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fullstory/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fxmarket"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gcp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gcpapplicationdefaultcredentials"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geckoboard"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gemini"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gengo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geoapify"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geocode"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geocodify"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geocodio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geoipifi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/getgeoapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/getgist"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/getresponse"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/getsandbox"
- githubv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/github/v1"
- githubv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/github/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/github_oauth2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/githubapp"
- gitlabv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab/v1"
- gitlabv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab/v2"
- gitlabv3 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab/v3"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitter"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/glassnode"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gocanvas"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gocardless"
- godaddyv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/godaddy/v1"
- godaddyv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/godaddy/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/goodday"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/googleoauth2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/grafana"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/grafanaserviceaccount"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/graphcms"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/graphhopper"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/groovehq"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/groq"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gtmetrix"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/guardianapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gumroad"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gyazo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/happyscribe"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/harness"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/harvest"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hashicorpvaultauth"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hasura"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hellosign"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/helpcrunch"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/helpscout"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hereapi"
- heroku_v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/heroku/v1"
- heroku_v2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/heroku/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hiveage"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/holidayapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/holistic"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/honeycomb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/host"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/html2pdf"
- hubspot_apikey_v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hubspot_apikey/v1"
- hubspot_apikey_v2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hubspot_apikey/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/huggingface"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/humanity"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hunter"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hybiscus"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hypertrack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/iconfinder"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/iexapis"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/iexcloud"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/imagekit"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/imagga"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/impala"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/infura"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/insightly"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/instabot"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/instamojo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/intercom"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/interseller"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/intra42"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/intrinio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/invoiceocean"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ip2location"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipgeolocation"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipinfodb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipquality"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipstack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jdbc"
- jiratokenv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v1"
- jiratokenv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jotform"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jumpcloud"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jupiterone"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/juro"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jwt"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kanban"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kanbantool"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/karmacrm"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/keenio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kickbox"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/klaviyo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/klipfolio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/knapsackpro"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kontent"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kraken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kucoin"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kylas"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/langfuse"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/langsmith"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/languagelayer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/larksuite"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/larksuiteapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/launchdarkly"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ldap"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/leadfeeder"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lemlist"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lemonsqueezy"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lendflow"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lessannoyingcrm"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lexigram"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/linearapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/linenotify"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/linkpreview"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/liveagent"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/livestorm"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/loadmill"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/locationiq"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/loggly"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/loginradius"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/logzio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lokalisetoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/loyverse"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lunchmoney"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/luno"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/madkudu"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/magicbell"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailboxlayer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailchimp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailerlite"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailgun"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailjetbasicauth"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailjetsms"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailmodo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailsac"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mandrill"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mapbox"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mapquest"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/marketstack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mattermostpersonaltoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mavenlink"
- maxmindlicensev1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/maxmindlicense/v1"
- maxmindlicensev2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/maxmindlicense/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/meaningcloud"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mediastack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/meistertask"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/meraki"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mesibo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/messagebird"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/metaapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/metabase"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/metrilo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/microsoftteamswebhook"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mindmeister"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/miro"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mite"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mixmax"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mockaroo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/moderation"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/monday"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mongodb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/monkeylearn"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/moonclerk"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/moosend"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/moralis"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mrticktock"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mux"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/myfreshworks"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/myintervals"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nethunt"
- netlifyv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/netlify/v1"
- netlifyv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/netlify/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/netsuite"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/neutrinoapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/newrelicpersonalapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/newsapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/newscatcher"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nexmoapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nftport"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ngc"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ngrok"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nicereply"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nightfall"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nimble"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/noticeable"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/notion"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nozbeteams"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/npmtoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/npmtokenv2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nugetapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/numverify"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nutritionix"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nvapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nylas"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/oanda"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/okta"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/omnisend"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/onedesk"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/onelogin"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/onepagecrm"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/onesignal"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/onfleet"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/oopspam"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/openai"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/opencagedata"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/openuv"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/openvpn"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/openweather"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/opsgenie"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/optimizely"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/overloop"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/owlbot"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/packagecloud"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pagarme"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pagerdutyapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pandadoc"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pandascore"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paperform"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paralleldots"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/parsehub"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/parsers"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/parseur"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/partnerstack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pastebin"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paydirtapp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paymoapp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paymongo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paypaloauth"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paystack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pdflayer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pdfshift"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/peopledatalabs"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pepipost"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/percy"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/photoroom"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/phraseaccesstoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pinata"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pipedream"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pipedrive"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pivotaltracker"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pixabay"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/plaidkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/planetscale"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/planetscaledb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/planviewleankit"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/planyo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/plivo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/podio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pollsapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/poloniex"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/polygon"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/portainer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/portainertoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/positionstack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postageapp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postbacks"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postgres"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/posthog"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postman"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postmark"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/powrbot"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/prefect"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/privacy"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/privatekey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/prodpad"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/prospectcrm"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/protocolsio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/proxycrawl"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pubnubpublishkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pubnubsubscriptionkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pulumi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/purestake"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pushbulletapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pusherchannelkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pypi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/qase"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/qualaroo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/qubole"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rabbitmq"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/railwayapp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ramp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rapidapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rawg"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/razorpay"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/reachmail"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/readme"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/reallysimplesystems"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rebrandly"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rechargepayments"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/redis"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/refiner"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rentman"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/repairshopr"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/replicate"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/replyio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/requestfinance"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/restpackhtmltopdfapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/restpackscreenshotapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/revampcrm"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ringcentral"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ritekit"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/roaring"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/robinhoodcrypto"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rocketreach"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rootly"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/route4me"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rownd"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rubygems"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/runrunit"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/saladcloudapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesblink"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salescookie"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesflare"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesforce"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesforceoauth2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesforcerefreshtoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesmate"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sanity"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/satismeterprojectkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/satismeterwritekey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/saucelabs"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scalewaykey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scalr"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapeowl"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scraperapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scraperbox"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapestack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapfly"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapingant"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapingbee"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/screenshotapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/screenshotlayer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrutinizerci"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/securitytrails"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/segmentapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/selectpdf"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/semaphore"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sendbird"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sendbirdorganizationapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sendgrid"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sendinbluev2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sentryorgtoken"
- sentrytokenv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sentrytoken/v1"
- sentrytokenv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sentrytoken/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/serphouse"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/serpstack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sheety"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sherpadesk"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shipday"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shodankey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shopify"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shortcut"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shotstack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shutterstock"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shutterstockoauth"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/signable"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/signalwire"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/signaturit"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/signupgenius"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sigopt"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/simfin"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/simplesat"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/simplynoted"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/simvoly"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sinchmessage"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sirv"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/siteleaf"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/skrappio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/skybiometry"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/slack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/slackwebhook"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/smartsheets"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/smartystreets"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/smooch"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/snipcart"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/snowflake"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/snykkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sonarcloud"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sourcegraph"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sourcegraphcody"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/speechtextai"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/splunkobservabilitytoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/spoonacular"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sportsmonk"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sqlserver"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/square"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/squareapp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/squarespace"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/squareup"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sslmate"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/statuscake"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/statuspage"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/statuspal"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stitchdata"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stockdata"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/storecove"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stormboard"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stormglass"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/storyblok"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/storyblokpersonalaccesstoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/storychief"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/strava"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/streak"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stripe"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stripepaymentintent"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stripo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stytch"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sugester"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sumologickey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/supabasetoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/supernotesapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/surveyanyplace"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/surveybot"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/surveysparrow"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/survicate"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/swell"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/swiftype"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tableau"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tailscale"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tallyfy"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tatumio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/taxjar"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/teamgate"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/teamworkcrm"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/teamworkdesk"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/teamworkspaces"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/technicalanalysisapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tefter"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/telegrambottoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/teletype"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/telnyx"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/terraformcloudpersonaltoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/testingbot"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/textmagic"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/theoddsapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/thinkific"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/thousandeyes"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ticketmaster"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tickettailor"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tiingo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/timecamp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/timezoneapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tineswebhook"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tmetric"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/todoist"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tokeet"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tomorrowio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tomtom"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tradier"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/transferwise"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/travelpayouts"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/travisci"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/trelloapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/trufflehogenterprise"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twelvedata"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twilio"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twilioapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twist"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitch"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitchaccesstoken"
- twitterv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitter/v1"
- twitterv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitter/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitterconsumerkey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tyntec"
- typeformv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/typeform/v1"
- typeformv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/typeform/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/typetalk"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ubidots"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/uclassify"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/unifyid"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/unplugg"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/unsplash"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/upcdatabase"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/uplead"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/uploadcare"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/uptimerobot"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/upwave"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/uri"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/urlscan"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/userflow"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/userstack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vagrantcloudpersonaltoken"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vatlayer"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vbout"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vercel"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/verifier"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/verimail"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/veriphone"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/versioneye"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/viewneo"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/virustotal"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/visualcrossing"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/voiceflow"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/voicegain"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/voodoosms"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vouchery"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vpnapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vultrapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vyte"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/walkscore"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/weatherbit"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/weatherstack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/web3storage"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/webex"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/webexbot"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/webflow"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/webscraper"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/webscraping"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/websitepulse"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/weightsandbiases"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/whoxy"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/wistia"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/wiz"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/worksnaps"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/workstack"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/worldcoinindex"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/worldweather"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/wrike"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/xai"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/yandex"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/yelp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/youneedabudget"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/yousign"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/youtubeapikey"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zendeskapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zenkitapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zenrows"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zenscrape"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zenserp"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zeplin"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zerobounce"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zerotier"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zipapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zipbooks"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zipcodeapi"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zipcodebase"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zohocrm"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zonkafeedback"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zulipchat"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func buildDetectorList() []detectors.Detector {
- return []detectors.Detector{
- &abyssale.Scanner{},
- // &abstract.Scanner{},
- &abuseipdb.Scanner{},
- &accuweatherv1.Scanner{},
- &accuweatherv2.Scanner{},
- &adafruitio.Scanner{},
- // &adobeio.Scanner{},
- &adzuna.Scanner{},
- &aeroworkflow.Scanner{},
- &agora.Scanner{},
- &aha.Scanner{},
- &airbrakeprojectkey.Scanner{},
- &airbrakeuserkey.Scanner{},
- &airship.Scanner{},
- &airtableoauth.Scanner{},
- &airtablepersonalaccesstoken.Scanner{},
- &airvisual.Scanner{},
- &aiven.Scanner{},
- &alchemy.Scanner{},
- // The service currently has blocked requests with a "TruffleHog" UserAgent.
- // &alconost.Scanner{},
- &alegra.Scanner{},
- &aletheiaapi.Scanner{},
- &algoliaadminkey.Scanner{},
- &alibaba.Scanner{},
- &alienvault.Scanner{},
- &allsports.Scanner{},
- &amadeus.Scanner{},
- &ambee.Scanner{},
- &litudeapikey.Scanner{},
- &anthropic.Scanner{},
- &anypoint.Scanner{},
- &apacta.Scanner{},
- &api2cart.Scanner{},
- &apideck.Scanner{},
- &apiflash.Scanner{},
- &apifonica.Scanner{},
- &apify.Scanner{},
- &apilayer.Scanner{},
- &apimatic.Scanner{},
- &apimetrics.Scanner{},
- &apitemplate.Scanner{},
- // &apollo.Scanner{},
- &appcues.Scanner{},
- &appfollow.Scanner{},
- &appointedd.Scanner{},
- &appoptics.Scanner{},
- &appsynergy.Scanner{},
- &apptivo.Scanner{},
- &artifactory.Scanner{},
- &artsy.Scanner{},
- &asanaoauth.Scanner{},
- &asanapersonalaccesstoken.Scanner{},
- &assemblyai.Scanner{},
- &atera.Scanner{},
- &atlassianv1.Scanner{},
- &atlassianv2.Scanner{},
- &audd.Scanner{},
- &auth0managementapitoken.Scanner{},
- &auth0oauth.Scanner{},
- &autodesk.Scanner{},
- &autoklose.Scanner{},
- &autopilot.Scanner{},
- &avazapersonalaccesstoken.Scanner{},
- &aviationstack.Scanner{},
- aws_access_keys.New(),
- aws_session_keys.New(),
- &axonaut.Scanner{},
- &aylien.Scanner{},
- &ayrshare.Scanner{},
- &azureapimanagementsubscriptionkey.Scanner{},
- &azure_entra_refreshtoken.Scanner{},
- &azure_entra_serviceprincipal_v1.Scanner{},
- &azure_entra_serviceprincipal_v2.Scanner{},
- &azure_batch.Scanner{},
- &azureappconfigconnectionstring.Scanner{},
- &azure_cosmosdb.Scanner{},
- &azurecontainerregistry.Scanner{},
- &azuredevopspersonalaccesstoken.Scanner{},
- &azuredirectmanagementkey.Scanner{},
- // &azurefunctionkey.Scanner{}, // detector is throwing some FPs
- &azure_openai.Scanner{},
- &azuresastoken.Scanner{},
- &azuresearchadminkey.Scanner{},
- &azuresearchquerykey.Scanner{},
- &azure_storage.Scanner{},
- &azurerepositorykey.Scanner{},
- &bannerbearv1.Scanner{},
- &bannerbearv2.Scanner{},
- &baremetrics.Scanner{},
- &beamer.Scanner{},
- &beebole.Scanner{},
- // Besnappy appears to be abandoned. The domain has expired. API returns 200 OK for all secrets causing FPs
- // &besnappy.Scanner{},
- &besttime.Scanner{},
- &betterstack.Scanner{},
- &billomat.Scanner{},
- &bingsubscriptionkey.Scanner{},
- &bitbar.Scanner{},
- &bitbucketapppassword.Scanner{},
- &bitcoinaverage.Scanner{},
- &bitfinex.Scanner{},
- &bitlyaccesstoken.Scanner{},
- &bitmex.Scanner{},
- &blazemeter.Scanner{},
- &blitapp.Scanner{},
- // &blocknative.Scanner{}, // temporary disabled due to API issue
- &blogger.Scanner{},
- &bombbomb.Scanner{},
- &boostnote.Scanner{},
- &borgbase.Scanner{},
- &box.Scanner{},
- &boxoauth.Scanner{},
- &braintreepayments.Scanner{},
- &brandfetchv1.Scanner{},
- &brandfetchv2.Scanner{},
- &browserstack.Scanner{},
- &browshot.Scanner{},
- &bscscan.Scanner{},
- &buddyns.Scanner{},
- &budibase.Scanner{},
- &bugherd.Scanner{},
- &bugsnag.Scanner{},
- &buildKitev1.Scanner{},
- &buildKitev2.Scanner{},
- &bulbul.Scanner{},
- &bulksms.Scanner{},
- &buttercms.Scanner{},
- &caflou.Scanner{},
- &calendarific.Scanner{},
- &calendlyapikey.Scanner{},
- &calorieninja.Scanner{},
- &campayn.Scanner{},
- &cannyio.Scanner{},
- &capsulecrm.Scanner{},
- &captainDataV1.Scanner{},
- &captainDataV2.Scanner{},
- &carboninterface.Scanner{},
- &cashboard.Scanner{},
- &caspio.Scanner{},
- &censys.Scanner{},
- ¢ralstationcrm.Scanner{},
- &cexio.Scanner{},
- &chartmogul.Scanner{},
- &chatbot.Scanner{},
- &chatfule.Scanner{},
- &checio.Scanner{},
- &checklyhq.Scanner{},
- &checkout.Scanner{},
- &checkvist.Scanner{},
- &cicero.Scanner{},
- &circleciV1.Scanner{},
- &circleciV2.Scanner{},
- &clarifai.Scanner{},
- &clearbit.Scanner{},
- &clickhelp.Scanner{},
- &clicksendsms.Scanner{},
- &clickuppersonaltoken.Scanner{},
- &cliengo.Scanner{},
- &clientary.Scanner{},
- &clinchpad.Scanner{},
- &clockify.Scanner{},
- &clockworksms.Scanner{},
- &closecrm.Scanner{},
- &cloudconvert.Scanner{},
- &cloudelements.Scanner{},
- &cloudflareapitoken.Scanner{},
- &cloudflarecakey.Scanner{},
- &cloudflareglobalapikey.Scanner{},
- &cloudimage.Scanner{},
- &cloudmersive.Scanner{},
- &cloudplan.Scanner{},
- &cloudsmith.Scanner{},
- &cloverly.Scanner{},
- &cloze.Scanner{},
- &clustdoc.Scanner{},
- &coda.Scanner{},
- &codacy.Scanner{},
- &codeclimate.Scanner{},
- &codemagic.Scanner{},
- &codequiry.Scanner{},
- &coinapi.Scanner{},
- &coinbase.Scanner{},
- &coinlayer.Scanner{},
- &coinlib.Scanner{},
- &collect2.Scanner{},
- &column.Scanner{},
- &commercejs.Scanner{},
- &commodities.Scanner{},
- &companyhub.Scanner{},
- &confluent.Scanner{},
- &contentfulpersonalaccesstoken.Scanner{},
- &conversiontools.Scanner{},
- &convertapi.Scanner{},
- &convertkit.Scanner{},
- &convier.Scanner{},
- &copper.Scanner{},
- &couchbase.Scanner{},
- &countrylayer.Scanner{},
- &courier.Scanner{},
- &coveralls.Scanner{},
- &craftmypdf.Scanner{},
- &crowdin.Scanner{},
- &cryptocompare.Scanner{},
- ¤cycloud.Scanner{},
- ¤cyfreaks.Scanner{},
- ¤cylayer.Scanner{},
- ¤cyscoop.Scanner{},
- ¤tsapi.Scanner{},
- &customerguru.Scanner{},
- &customerio.Scanner{},
- &d7network.Scanner{},
- // &dailyco.Scanner{},
- &dandelion.Scanner{},
- &dareboost.Scanner{},
- &databox.Scanner{},
- &databrickstoken.Scanner{},
- &datadogtoken.Scanner{},
- &datagov.Scanner{},
- // &debounce.Scanner{},
- &deepai.Scanner{},
- &deepgram.Scanner{},
- &deepseek.Scanner{},
- &delighted.Scanner{},
- &demio.Scanner{},
- &denodeploy.Scanner{},
- &deputy.Scanner{},
- &detectify.Scanner{},
- &detectlanguage.Scanner{},
- &dfuse.Scanner{},
- &diffbot.Scanner{},
- &diggernaut.Scanner{},
- &digitaloceantoken.Scanner{},
- &digitaloceanv2.Scanner{},
- &discordbottoken.Scanner{},
- &discordwebhook.Scanner{},
- &disqus.Scanner{},
- &ditto.Scanner{},
- &dnscheck.Scanner{},
- &docker.Scanner{},
- &dockerhubv1.Scanner{},
- &dockerhubv2.Scanner{},
- &docparser.Scanner{},
- &documo.Scanner{},
- &docusign.Scanner{},
- &doppler.Scanner{},
- &dotdigital.Scanner{},
- &dovico.Scanner{},
- &dronahq.Scanner{},
- &droneci.Scanner{},
- &dropbox.Scanner{},
- &duply.Scanner{},
- &dwolla.Scanner{},
- &dynalist.Scanner{},
- &dyspatch.Scanner{},
- &eagleeyenetworks.Scanner{},
- &easyinsight.Scanner{},
- &ecostruxureit.Scanner{},
- &edamam.Scanner{},
- &edenai.Scanner{},
- &eightxeight.Scanner{},
- &elasticemail.Scanner{},
- &elevenlabsv1.Scanner{},
- &elevenlabsv2.Scanner{},
- &enablex.Scanner{},
- &endorlabs.Scanner{},
- &enigma.Scanner{},
- &envoyapikey.Scanner{},
- &eraser.Scanner{},
- ðerscan.Scanner{},
- ðplorer.Scanner{},
- &eventbrite.Scanner{},
- &everhour.Scanner{},
- &exchangerateapi.Scanner{},
- &exchangeratesapi.Scanner{},
- &exportsdk.Scanner{},
- &extractorapi.Scanner{},
- &facebookoauth.Scanner{},
- &faceplusplus.Scanner{},
- &fastforex.Scanner{},
- &fastlypersonaltoken.Scanner{},
- &feedier.Scanner{},
- &fetchrss.Scanner{},
- &fibery.Scanner{},
- &figmapersonalaccesstokenv1.Scanner{},
- &figmapersonalaccesstokenv2.Scanner{},
- &fileio.Scanner{},
- &finage.Scanner{},
- &financialmodelingprep.Scanner{},
- &findl.Scanner{},
- &finnhub.Scanner{},
- &fixerio.Scanner{},
- &flatio.Scanner{},
- &fleetbase.Scanner{},
- &flexport.Scanner{},
- &flickr.Scanner{},
- &flightapi.Scanner{},
- &flightlabs.Scanner{},
- &flightstats.Scanner{},
- &float.Scanner{},
- &flowflu.Scanner{},
- &flutterwave.Scanner{},
- &flyio.Scanner{},
- &fmfw.Scanner{},
- &formbucket.Scanner{},
- &formcraft.Scanner{},
- &formio.Scanner{},
- &formsite.Scanner{},
- &foursquare.Scanner{},
- &frameio.Scanner{},
- &freshbooks.Scanner{},
- &freshdesk.Scanner{},
- &front.Scanner{},
- &ftp.Scanner{},
- &fulcrum.Scanner{},
- &fullstoryv1.Scanner{},
- &fullstoryv2.Scanner{},
- &fxmarket.Scanner{},
- &gcp.Scanner{},
- &gcpapplicationdefaultcredentials.Scanner{},
- &geckoboard.Scanner{},
- &gemini.Scanner{},
- // &generic.Scanner{},
- &gengo.Scanner{},
- &geoapify.Scanner{},
- &geocode.Scanner{},
- &geocodify.Scanner{},
- &geocodio.Scanner{},
- &geoipifi.Scanner{},
- // &getemail.Scanner{},
- // &getemails.Scanner{},
- &getgeoapi.Scanner{},
- &getgist.Scanner{},
- &getresponse.Scanner{},
- &getsandbox.Scanner{},
- &github_oauth2.Scanner{},
- &githubapp.Scanner{},
- &githubv1.Scanner{},
- &githubv2.Scanner{},
- &gitlabv1.Scanner{},
- &gitlabv2.Scanner{},
- &gitlabv3.Scanner{},
- &gitter.Scanner{},
- &glassnode.Scanner{},
- &gocanvas.Scanner{},
- &gocardless.Scanner{},
- &godaddyv1.Scanner{},
- &godaddyv2.Scanner{},
- &goodday.Scanner{},
- &googleoauth2.Scanner{},
- &grafana.Scanner{},
- &grafanaserviceaccount.Scanner{},
- &graphcms.Scanner{},
- &graphhopper.Scanner{},
- &groovehq.Scanner{},
- &groq.Scanner{},
- >metrix.Scanner{},
- &guardianapi.Scanner{},
- &gumroad.Scanner{},
- &gyazo.Scanner{},
- &happyscribe.Scanner{},
- &harness.Scanner{},
- &harvest.Scanner{},
- &hashicorpvaultauth.Scanner{},
- &hasura.Scanner{},
- &hellosign.Scanner{},
- &helpcrunch.Scanner{},
- &helpscout.Scanner{},
- &hereapi.Scanner{},
- &heroku_v1.Scanner{},
- &heroku_v2.Scanner{},
- // &hive.Scanner{},
- &hiveage.Scanner{},
- &holidayapi.Scanner{},
- &holistic.Scanner{},
- &honeycomb.Scanner{},
- &host.Scanner{},
- &html2pdf.Scanner{},
- &hubspot_apikey_v1.Scanner{},
- &hubspot_apikey_v2.Scanner{},
- &huggingface.Scanner{},
- &humanity.Scanner{},
- &hunter.Scanner{},
- &hybiscus.Scanner{},
- &hypertrack.Scanner{},
- // &ibmclouduserkey.Scanner{},
- &iconfinder.Scanner{},
- &iexapis.Scanner{},
- ¡oud.Scanner{},
- &imagekit.Scanner{},
- &imagga.Scanner{},
- &impala.Scanner{},
- &infura.Scanner{},
- &insightly.Scanner{},
- &instabot.Scanner{},
- &instamojo.Scanner{},
- &intercom.Scanner{},
- &interseller.Scanner{},
- &intra42.Scanner{},
- &intrinio.Scanner{},
- &invoiceocean.Scanner{},
- &ip2location.Scanner{},
- &ipapi.Scanner{},
- &ipgeolocation.Scanner{},
- &ipinfodb.Scanner{},
- &ipquality.Scanner{},
- &ipstack.Scanner{},
- &jdbc.Scanner{},
- &jiratokenv1.Scanner{},
- &jiratokenv2.Scanner{},
- &jotform.Scanner{},
- &jumpcloud.Scanner{},
- &jupiterone.Scanner{},
- &juro.Scanner{},
- &jwt.Scanner{},
- &kanban.Scanner{},
- &kanbantool.Scanner{},
- &karmacrm.Scanner{},
- &keenio.Scanner{},
- &kickbox.Scanner{},
- &klaviyo.Scanner{},
- &klipfolio.Scanner{},
- &knapsackpro.Scanner{},
- &kontent.Scanner{},
- &kraken.Scanner{},
- &kucoin.Scanner{},
- &kylas.Scanner{},
- &langfuse.Scanner{},
- &langsmith.Scanner{},
- &languagelayer.Scanner{},
- &larksuite.Scanner{},
- &larksuiteapikey.Scanner{},
- &launchdarkly.Scanner{},
- &ldap.Scanner{},
- &leadfeeder.Scanner{},
- &lemlist.Scanner{},
- &lemonsqueezy.Scanner{},
- &lendflow.Scanner{},
- &lessannoyingcrm.Scanner{},
- &lexigram.Scanner{},
- &linearapi.Scanner{},
- // &linemessaging.Scanner{},
- &linenotify.Scanner{},
- &linkpreview.Scanner{},
- &liveagent.Scanner{},
- &livestorm.Scanner{},
- &loadmill.Scanner{},
- &locationiq.Scanner{},
- &loggly.Scanner{},
- &loginradius.Scanner{},
- &logzio.Scanner{},
- &lokalisetoken.Scanner{},
- &loyverse.Scanner{},
- &lunchmoney.Scanner{},
- &luno.Scanner{},
- // &m3o.Scanner{},
- &madkudu.Scanner{},
- &magicbell.Scanner{},
- // &magnetic.Scanner{},
- &mailboxlayer.Scanner{},
- &mailchimp.Scanner{},
- &mailerlite.Scanner{},
- &mailgun.Scanner{},
- &mailjetbasicauth.Scanner{},
- &mailjetsms.Scanner{},
- &mailmodo.Scanner{},
- &mailsac.Scanner{},
- &mandrill.Scanner{},
- // &manifest.Scanner{},
- &mapbox.Scanner{},
- &mapquest.Scanner{},
- &marketstack.Scanner{},
- &mattermostpersonaltoken.Scanner{},
- &mavenlink.Scanner{},
- &maxmindlicensev1.Scanner{},
- &maxmindlicensev2.Scanner{},
- &meaningcloud.Scanner{},
- &mediastack.Scanner{},
- &meistertask.Scanner{},
- &meraki.Scanner{},
- &mesibo.Scanner{},
- &messagebird.Scanner{},
- &metaapi.Scanner{},
- &metabase.Scanner{},
- &metrilo.Scanner{},
- µsoftteamswebhook.Scanner{},
- &mindmeister.Scanner{},
- &miro.Scanner{},
- &mite.Scanner{},
- &mixmax.Scanner{},
- // &mixpanel.Scanner{},
- &mockaroo.Scanner{},
- &moderation.Scanner{},
- &monday.Scanner{},
- &mongodb.Scanner{},
- &monkeylearn.Scanner{},
- &moonclerk.Scanner{},
- &moosend.Scanner{},
- &moralis.Scanner{},
- &mrticktock.Scanner{},
- &mux.Scanner{},
- &myfreshworks.Scanner{},
- &myintervals.Scanner{},
- // &nasdaqdatalink.Scanner{},
- &nethunt.Scanner{},
- &netlifyv1.Scanner{},
- &netlifyv2.Scanner{},
- &netsuite.Scanner{},
- &neutrinoapi.Scanner{},
- &newrelicpersonalapikey.Scanner{},
- &newsapi.Scanner{},
- &newscatcher.Scanner{},
- &nexmoapikey.Scanner{},
- &nftport.Scanner{},
- &ngc.Scanner{},
- &ngrok.Scanner{},
- &nicereply.Scanner{},
- &nightfall.Scanner{},
- &nimble.Scanner{},
- ¬iceable.Scanner{},
- ¬ion.Scanner{},
- &nozbeteams.Scanner{},
- &npmtoken.Scanner{},
- &npmtokenv2.Scanner{},
- &nugetapikey.Scanner{},
- &numverify.Scanner{},
- &nutritionix.Scanner{},
- &nvapi.Scanner{},
- &nylas.Scanner{},
- &oanda.Scanner{},
- &okta.Scanner{},
- &omnisend.Scanner{},
- &onedesk.Scanner{},
- &onelogin.Scanner{},
- &onepagecrm.Scanner{},
- &onesignal.Scanner{},
- &onfleet.Scanner{},
- &oopspam.Scanner{},
- &openai.Scanner{},
- &opencagedata.Scanner{},
- &openuv.Scanner{},
- &openvpn.Scanner{},
- &openweather.Scanner{},
- &opsgenie.Scanner{},
- &optimizely.Scanner{},
- &overloop.Scanner{},
- &owlbot.Scanner{},
- &packagecloud.Scanner{},
- &pagarme.Scanner{},
- &pagerdutyapikey.Scanner{},
- &pandadoc.Scanner{},
- &pandascore.Scanner{},
- &paperform.Scanner{},
- ¶lleldots.Scanner{},
- &parsehub.Scanner{},
- &parsers.Scanner{},
- &parseur.Scanner{},
- &partnerstack.Scanner{},
- &pastebin.Scanner{},
- &paydirtapp.Scanner{},
- &paymoapp.Scanner{},
- &paymongo.Scanner{},
- &paypaloauth.Scanner{},
- &paystack.Scanner{},
- &pdflayer.Scanner{},
- &pdfshift.Scanner{},
- &peopledatalabs.Scanner{},
- &pepipost.Scanner{},
- &percy.Scanner{},
- &photoroom.Scanner{},
- &phraseaccesstoken.Scanner{},
- &pinata.Scanner{},
- &pipedream.Scanner{},
- &pipedrive.Scanner{},
- &pivotaltracker.Scanner{},
- &pixabay.Scanner{},
- &plaidkey.Scanner{},
- &planetscale.Scanner{},
- &planetscaledb.Scanner{},
- &planviewleankit.Scanner{},
- &planyo.Scanner{},
- &plivo.Scanner{},
- &podio.Scanner{},
- &pollsapi.Scanner{},
- &poloniex.Scanner{},
- &polygon.Scanner{},
- &portainer.Scanner{},
- &portainertoken.Scanner{},
- &positionstack.Scanner{},
- &postageapp.Scanner{},
- &postbacks.Scanner{},
- &postgres.Scanner{},
- &posthog.Scanner{},
- &postman.Scanner{},
- &postmark.Scanner{},
- &powrbot.Scanner{},
- &prefect.Scanner{},
- &privacy.Scanner{},
- &privatekey.Scanner{},
- &prodpad.Scanner{},
- &prospectcrm.Scanner{},
- &protocolsio.Scanner{},
- &proxycrawl.Scanner{},
- &pubnubpublishkey.Scanner{},
- &pubnubsubscriptionkey.Scanner{},
- &pulumi.Scanner{},
- &purestake.Scanner{},
- &pushbulletapikey.Scanner{},
- &pusherchannelkey.Scanner{},
- &pypi.Scanner{},
- &qase.Scanner{},
- &qualaroo.Scanner{},
- &qubole.Scanner{},
- &rabbitmq.Scanner{},
- &railwayapp.Scanner{},
- &ramp.Scanner{},
- &rapidapi.Scanner{},
- // &raven.Scanner{},
- &rawg.Scanner{},
- &razorpay.Scanner{},
- &reachmail.Scanner{},
- &readme.Scanner{},
- &reallysimplesystems.Scanner{},
- &rebrandly.Scanner{},
- &rechargepayments.Scanner{},
- &redis.Scanner{},
- &refiner.Scanner{},
- &rentman.Scanner{},
- &repairshopr.Scanner{},
- &replicate.Scanner{},
- &replyio.Scanner{},
- &requestfinance.Scanner{},
- // &restpack.Scanner{},
- &restpackhtmltopdfapi.Scanner{},
- &restpackscreenshotapi.Scanner{},
- &revampcrm.Scanner{},
- &ringcentral.Scanner{},
- &ritekit.Scanner{},
- &roaring.Scanner{},
- &robinhoodcrypto.Scanner{},
- &rocketreach.Scanner{},
- // &rockset.Scanner{},
- &rootly.Scanner{},
- &route4me.Scanner{},
- &rownd.Scanner{},
- &rubygems.Scanner{},
- &runrunit.Scanner{},
- &saladcloudapikey.Scanner{},
- &salesblink.Scanner{},
- &salescookie.Scanner{},
- &salesflare.Scanner{},
- &salesforce.Scanner{},
- &salesforceoauth2.Scanner{},
- &salesforcerefreshtoken.Scanner{},
- &salesmate.Scanner{},
- &sanity.Scanner{},
- &satismeterprojectkey.Scanner{},
- &satismeterwritekey.Scanner{},
- &saucelabs.Scanner{},
- &scalewaykey.Scanner{},
- &scalr.Scanner{},
- &scrapeowl.Scanner{},
- &scraperapi.Scanner{},
- &scraperbox.Scanner{},
- &scrapestack.Scanner{},
- &scrapfly.Scanner{},
- &scrapingant.Scanner{},
- &scrapingbee.Scanner{},
- &screenshotapi.Scanner{},
- &screenshotlayer.Scanner{},
- &scrutinizerci.Scanner{},
- &securitytrails.Scanner{},
- &segmentapikey.Scanner{},
- &selectpdf.Scanner{},
- &semaphore.Scanner{},
- &sendbird.Scanner{},
- &sendbirdorganizationapi.Scanner{},
- &sendgrid.Scanner{},
- &sendinbluev2.Scanner{},
- &sentrytokenv1.Scanner{},
- &sentrytokenv2.Scanner{},
- &sentryorgtoken.Scanner{},
- &serphouse.Scanner{},
- &serpstack.Scanner{},
- &sheety.Scanner{},
- &sherpadesk.Scanner{},
- &shipday.Scanner{},
- &shodankey.Scanner{},
- &shopify.Scanner{},
- &shortcut.Scanner{},
- &shotstack.Scanner{},
- &shutterstock.Scanner{},
- &shutterstockoauth.Scanner{},
- &signable.Scanner{},
- &signalwire.Scanner{},
- &signaturit.Scanner{},
- &signupgenius.Scanner{},
- &sigopt.Scanner{},
- &simfin.Scanner{},
- &simplesat.Scanner{},
- &simplynoted.Scanner{},
- &simvoly.Scanner{},
- &sinchmessage.Scanner{},
- &sirv.Scanner{},
- &siteleaf.Scanner{},
- &skrappio.Scanner{},
- &skybiometry.Scanner{},
- &slack.Scanner{}, // has 4 secret types
- &slackwebhook.Scanner{},
- &smartsheets.Scanner{},
- &smartystreets.Scanner{},
- &smooch.Scanner{},
- &snipcart.Scanner{},
- &snowflake.Scanner{},
- &snykkey.Scanner{},
- &sonarcloud.Scanner{},
- &sourcegraph.Scanner{},
- &sourcegraphcody.Scanner{},
- // &sparkpost.Scanner{},
- &speechtextai.Scanner{},
- &splunkobservabilitytoken.Scanner{},
- &spoonacular.Scanner{},
- &sportsmonk.Scanner{},
- // &spotifykey.Scanner{},
- &sqlserver.Scanner{},
- &square.Scanner{},
- &squareapp.Scanner{},
- &squarespace.Scanner{},
- &squareup.Scanner{},
- &sslmate.Scanner{},
- &statuscake.Scanner{},
- &statuspage.Scanner{},
- &statuspal.Scanner{},
- &stitchdata.Scanner{},
- &stockdata.Scanner{},
- &storecove.Scanner{},
- &stormboard.Scanner{},
- &stormglass.Scanner{},
- &storyblok.Scanner{},
- &storyblokpersonalaccesstoken.Scanner{},
- &storychief.Scanner{},
- &strava.Scanner{},
- &streak.Scanner{},
- &stripe.Scanner{},
- &stripepaymentintent.Scanner{},
- &stripo.Scanner{},
- &stytch.Scanner{},
- &sugester.Scanner{},
- &sumologickey.Scanner{},
- &supabasetoken.Scanner{},
- &supernotesapi.Scanner{},
- &surveyanyplace.Scanner{},
- &surveybot.Scanner{},
- &surveysparrow.Scanner{},
- &survicate.Scanner{},
- &swell.Scanner{},
- &swiftype.Scanner{},
- &tableau.Scanner{},
- &tailscale.Scanner{},
- &tallyfy.Scanner{},
- &tatumio.Scanner{},
- &taxjar.Scanner{},
- &teamgate.Scanner{},
- &teamworkcrm.Scanner{},
- &teamworkdesk.Scanner{},
- &teamworkspaces.Scanner{},
- &technicalanalysisapi.Scanner{},
- &tefter.Scanner{},
- &telegrambottoken.Scanner{},
- &teletype.Scanner{},
- &telnyx.Scanner{},
- &terraformcloudpersonaltoken.Scanner{},
- &testingbot.Scanner{},
- &textmagic.Scanner{},
- &theoddsapi.Scanner{},
- &thinkific.Scanner{},
- &thousandeyes.Scanner{},
- &ticketmaster.Scanner{},
- &tickettailor.Scanner{},
- &tiingo.Scanner{},
- &timecamp.Scanner{},
- &timezoneapi.Scanner{},
- &tineswebhook.Scanner{},
- &tmetric.Scanner{},
- &todoist.Scanner{},
- // &toggltrack.Scanner{},
- &tokeet.Scanner{},
- &tomorrowio.Scanner{},
- &tomtom.Scanner{},
- &tradier.Scanner{},
- &transferwise.Scanner{},
- &travelpayouts.Scanner{},
- &travisci.Scanner{},
- &trelloapikey.Scanner{},
- &trufflehogenterprise.Scanner{},
- &twelvedata.Scanner{},
- &twilio.Scanner{},
- &twilioapikey.Scanner{},
- &twist.Scanner{},
- &twitch.Scanner{},
- &twitchaccesstoken.Scanner{},
- &twitterconsumerkey.Scanner{},
- &twitterv1.Scanner{},
- &twitterv2.Scanner{},
- &tyntec.Scanner{},
- &typeformv1.Scanner{},
- &typeformv2.Scanner{},
- &typetalk.Scanner{},
- &ubidots.Scanner{},
- &uclassify.Scanner{},
- &unifyid.Scanner{},
- &unplugg.Scanner{},
- &unsplash.Scanner{},
- &upcdatabase.Scanner{},
- &uplead.Scanner{},
- &uploadcare.Scanner{},
- &uptimerobot.Scanner{},
- &upwave.Scanner{},
- &uri.Scanner{},
- &urlscan.Scanner{},
- &userflow.Scanner{},
- &userstack.Scanner{},
- &vagrantcloudpersonaltoken.Scanner{},
- &vatlayer.Scanner{},
- &vbout.Scanner{},
- &vercel.Scanner{},
- &verifier.Scanner{},
- &verimail.Scanner{},
- &veriphone.Scanner{},
- &versioneye.Scanner{},
- &viewneo.Scanner{},
- &virustotal.Scanner{},
- &visualcrossing.Scanner{},
- &voiceflow.Scanner{},
- &voicegain.Scanner{},
- &voodoosms.Scanner{},
- &vouchery.Scanner{},
- &vpnapi.Scanner{},
- &vultrapikey.Scanner{},
- &vyte.Scanner{},
- &walkscore.Scanner{},
- &weatherbit.Scanner{},
- &weatherstack.Scanner{},
- &web3storage.Scanner{},
- &webex.Scanner{},
- &webexbot.Scanner{},
- &webflow.Scanner{},
- &webscraper.Scanner{},
- &webscraping.Scanner{},
- &websitepulse.Scanner{},
- &weightsandbiases.Scanner{},
- // &wepay.Scanner{},
- &whoxy.Scanner{},
- &wistia.Scanner{},
- &wiz.Scanner{},
- &worksnaps.Scanner{},
- &workstack.Scanner{},
- &worldcoinindex.Scanner{},
- &worldweather.Scanner{},
- &xai.Scanner{},
- &wrike.Scanner{},
- &yandex.Scanner{},
- &yelp.Scanner{},
- &youneedabudget.Scanner{},
- &yousign.Scanner{},
- &youtubeapikey.Scanner{},
- // &zapierwebhook.Scanner{},
- &zendeskapi.Scanner{},
- &zenkitapi.Scanner{},
- &zenrows.Scanner{},
- &zenscrape.Scanner{},
- &zenserp.Scanner{},
- &zeplin.Scanner{},
- &zerobounce.Scanner{},
- &zerotier.Scanner{},
- &zipapi.Scanner{},
- &zipbooks.Scanner{},
- &zipcodeapi.Scanner{},
- &zipcodebase.Scanner{},
- &zohocrm.Scanner{},
- &zonkafeedback.Scanner{},
- &zulipchat.Scanner{},
- }
-}
-
-func DefaultDetectors() []detectors.Detector {
- detectorList := buildDetectorList()
-
- // Automatically initialize all detectors that implement
- // EndpointCustomizer and/or CloudProvider interfaces.
- for _, d := range detectorList {
- customizer, ok := d.(detectors.EndpointCustomizer)
- if !ok {
- continue
- }
- // Default to always use the cloud endpoints (if available) and the found endpoints.
- customizer.UseFoundEndpoints(true)
- customizer.UseCloudEndpoint(true)
- if cloudProvider, ok := d.(detectors.CloudProvider); ok {
- customizer.SetCloudEndpoint(cloudProvider.CloudEndpoint())
- }
- }
-
- return detectorList
-}
-
-func DefaultDetectorTypesImplementing[T any]() map[detectorspb.DetectorType]struct{} {
- out := make(map[detectorspb.DetectorType]struct{})
- for _, detector := range DefaultDetectors() {
- if _, ok := detector.(T); ok {
- out[detector.Type()] = struct{}{}
- }
- }
- return out
-}
diff --git a/pkg/engine/defaults/defaults_test.go b/pkg/engine/defaults/defaults_test.go
deleted file mode 100644
index cfb4c1c908d2..000000000000
--- a/pkg/engine/defaults/defaults_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package defaults
-
-import (
- "testing"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
-)
-
-func TestDefaultDetectorsHaveUniqueVersions(t *testing.T) {
- detectorTypeToVersions := make(map[detectorspb.DetectorType]map[int]struct{})
- addVersion := func(versions map[int]struct{}, version int) map[int]struct{} {
- if versions == nil {
- versions = make(map[int]struct{})
- }
- versions[version] = struct{}{}
- return versions
- }
- // Loop through all our default detectors and find the ones that
- // implement Versioner. Of those, check each version number is unique.
- for _, detector := range DefaultDetectors() {
- v, ok := detector.(detectors.Versioner)
- if !ok {
- continue
- }
- version := v.Version()
- key := detector.Type()
- if set, ok := detectorTypeToVersions[key]; ok && set != nil {
- if _, ok := set[version]; ok {
- t.Errorf("detector %q has duplicate version: %d", detectorspb.DetectorType_name[int32(key)], version)
- }
- }
- detectorTypeToVersions[key] = addVersion(detectorTypeToVersions[key], version)
- }
-}
-
-func TestDefaultDetectorTypesImplementing(t *testing.T) {
- isVersioner := DefaultDetectorTypesImplementing[detectors.Versioner]()
- for _, detector := range DefaultDetectors() {
- _, expectedOk := detector.(detectors.Versioner)
- _, gotOk := isVersioner[detector.Type()]
- if expectedOk == gotOk {
- continue
- }
- t.Errorf(
- "detector %q doesn't match expected",
- detectorspb.DetectorType_name[int32(detector.Type())],
- )
- }
-}
-
-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())],
- )
- }
-}
diff --git a/pkg/engine/docker.go b/pkg/engine/docker.go
deleted file mode 100644
index c269d056467b..000000000000
--- a/pkg/engine/docker.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package engine
-
-import (
- "runtime"
-
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/docker"
-)
-
-// ScanDocker scans a given docker connection.
-func (e *Engine) ScanDocker(ctx context.Context, c sources.DockerConfig) (sources.JobProgressRef, error) {
- connection := &sourcespb.Docker{
- Images: c.Images,
- ExcludePaths: c.ExcludePaths,
- Namespace: c.Namespace,
- RegistryToken: c.RegistryToken,
- }
-
- switch {
- case c.UseDockerKeychain:
- connection.Credential = &sourcespb.Docker_DockerKeychain{DockerKeychain: true}
- case len(c.BearerToken) > 0:
- connection.Credential = &sourcespb.Docker_BearerToken{BearerToken: c.BearerToken}
- default:
- connection.Credential = &sourcespb.Docker_Unauthenticated{}
- }
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal gitlab connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - docker"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, docker.SourceType)
-
- dockerSource := &docker.Source{}
- if err := dockerSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
- return sources.JobProgressRef{}, err
- }
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, dockerSource)
-}
diff --git a/pkg/engine/elasticsearch.go b/pkg/engine/elasticsearch.go
deleted file mode 100644
index 10bdf719389d..000000000000
--- a/pkg/engine/elasticsearch.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package engine
-
-import (
- "runtime"
-
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/elasticsearch"
-)
-
-// ScanElasticsearch scans a Elasticsearch installation.
-func (e *Engine) ScanElasticsearch(ctx context.Context, c sources.ElasticsearchConfig) (sources.JobProgressRef, error) {
- connection := &sourcespb.Elasticsearch{
- Nodes: c.Nodes,
- Username: c.Username,
- Password: c.Password,
- CloudId: c.CloudID,
- ApiKey: c.APIKey,
- ServiceToken: c.ServiceToken,
- IndexPattern: c.IndexPattern,
- QueryJson: c.QueryJSON,
- SinceTimestamp: c.SinceTimestamp,
- BestEffortScan: c.BestEffortScan,
- }
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal Elasticsearch connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - Elasticsearch"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, elasticsearch.SourceType)
-
- elasticsearchSource := &elasticsearch.Source{}
- if err := elasticsearchSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
- return sources.JobProgressRef{}, err
- }
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, elasticsearchSource)
-}
diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go
deleted file mode 100644
index dfe41ab1763a..000000000000
--- a/pkg/engine/engine.go
+++ /dev/null
@@ -1,1390 +0,0 @@
-// Check the [process flow](docs/process_flow.md) and [concurrency](docs/concurrency.md) docs for
-// something of a structural overview
-
-package engine
-
-import (
- "bytes"
- "errors"
- "fmt"
- "runtime"
- "strconv"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/adrg/strutil"
- "github.com/adrg/strutil/metrics"
- lru "github.com/hashicorp/golang-lru/v2"
- "google.golang.org/protobuf/proto"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults"
- "github.com/trufflesecurity/trufflehog/v3/pkg/giturl"
- "github.com/trufflesecurity/trufflehog/v3/pkg/output"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/verificationcache"
-)
-
-var detectionTimeout = detectors.DefaultResponseTimeout
-
-var errOverlap = errors.New(
- "More than one detector has found this result. For your safety, verification has been disabled." +
- "You can override this behavior by using the --allow-verification-overlap flag.",
-)
-
-// Metrics for the scan engine for external consumption.
-type Metrics struct {
- BytesScanned uint64
- ChunksScanned uint64
- VerifiedSecretsFound uint64
- UnverifiedSecretsFound uint64
- AvgDetectorTime map[string]time.Duration
-
- scanStartTime time.Time
- ScanDuration time.Duration
-}
-
-// runtimeMetrics for the scan engine for internal use by the engine.
-type runtimeMetrics struct {
- mu sync.RWMutex
- Metrics
- detectorAvgTime sync.Map
-}
-
-// getScanDuration returns the duration of the scan.
-// If the scan is still running, it returns the time since the scan started.
-func (m *Metrics) getScanDuration() time.Duration {
- if m.ScanDuration == 0 {
- return time.Since(m.scanStartTime)
- }
-
- return m.ScanDuration
-}
-
-// ResultsDispatcher is an interface for dispatching findings of detected results.
-// Implementations can vary from printing results to the console to sending results to an external system.
-type ResultsDispatcher interface {
- Dispatch(ctx context.Context, result detectors.ResultWithMetadata) error
-}
-
-// Printer is used to format found results and output them to the user. Ex JSON, plain text, etc.
-// Please note printer implementations SHOULD BE thread safe.
-type Printer interface {
- Print(ctx context.Context, r *detectors.ResultWithMetadata) error
-}
-
-// PrinterDispatcher wraps an existing Printer implementation and adapts it to the ResultsDispatcher interface.
-type PrinterDispatcher struct{ printer Printer }
-
-// NewPrinterDispatcher creates a new PrinterDispatcher instance with the provided Printer.
-func NewPrinterDispatcher(printer Printer) *PrinterDispatcher { return &PrinterDispatcher{printer} }
-
-// Dispatch sends the result to the printer.
-func (p *PrinterDispatcher) Dispatch(ctx context.Context, result detectors.ResultWithMetadata) error {
- return p.printer.Print(ctx, &result)
-}
-
-// Config used to configure the engine.
-type Config struct {
- // Number of concurrent scanner workers,
- // also serves as a multiplier for other worker types (e.g., detector workers, notifier workers)
- Concurrency int
-
- ConfiguredSources []sources.ConfiguredSource
- Decoders []decoders.Decoder
- Detectors []detectors.Detector
- DetectorVerificationOverrides map[config.DetectorID]bool
- IncludeDetectors string
- ExcludeDetectors string
- CustomVerifiersOnly bool
- VerifierEndpoints map[string]string
-
- // Verify determines whether the scanner will verify candidate secrets.
- Verify bool
-
- // Defines which results will be notified by the engine
- // (e.g., verified, unverified, unknown)
- Results map[string]struct{}
- LogFilteredUnverified bool
-
- // FilterEntropy filters out unverified results using Shannon entropy.
- FilterEntropy float64
- // FilterUnverified sets the filterUnverified flag on the engine. If set to
- // true, the engine will only return the first unverified result for a chunk for a detector.
- FilterUnverified bool
- ShouldScanEntireChunk bool
-
- Dispatcher ResultsDispatcher
-
- // SourceManager is used to manage the sources and units.
- // TODO (ahrav): Update this comment, i'm dumb and don't really know what else it does.
- SourceManager *sources.SourceManager
-
- // PrintAvgDetectorTime sets the printAvgDetectorTime flag on the engine. If set to
- // true, the engine will print the average time taken by each detector.
- // This option allows us to measure the time taken for each detector ONLY if
- // the engine is configured to print the results.
- // Calculating the average time taken by each detector is an expensive operation
- // and should be avoided unless specified by the user.
- PrintAvgDetectorTime bool
-
- // VerificationOverlap determines whether the scanner will attempt to verify candidate secrets
- // that have been detected by multiple detectors.
- // By default, it is set to true.
- VerificationOverlap bool
-
- // DetectorWorkerMultiplier is used to determine the number of detector workers to spawn.
- DetectorWorkerMultiplier int
-
- // NotificationWorkerMultiplier is used to determine the number of notification workers to spawn.
- NotificationWorkerMultiplier int
-
- // VerificationOverlapWorkerMultiplier is used to determine the number of verification overlap workers to spawn.
- VerificationOverlapWorkerMultiplier int
-
- VerificationResultCache verificationcache.ResultCache
- VerificationCacheMetrics verificationcache.MetricsReporter
-}
-
-// Engine represents the core scanning engine responsible for detecting secrets in input data.
-// It manages the lifecycle of the scanning process, including initialization, worker management,
-// and result notification. The engine is designed to be flexible and configurable, allowing for
-// customization through various options and configurations.
-type Engine struct {
- // CLI flags.
- concurrency int
- decoders []decoders.Decoder
- detectors []detectors.Detector
- verificationCache *verificationcache.VerificationCache
- // Any detectors configured to override sources' verification flags
- detectorVerificationOverrides map[config.DetectorID]bool
-
- // filterUnverified is used to reduce the number of unverified results.
- // If there are multiple unverified results for the same chunk for the same detector,
- // only the first one will be kept.
- filterUnverified bool
- // entropyFilter is used to filter out unverified results using Shannon entropy.
- filterEntropy float64
- notifyVerifiedResults bool
- notifyUnverifiedResults bool
- notifyUnknownResults bool
- retainFalsePositives bool
- verificationOverlap bool
- printAvgDetectorTime bool
- // By default, the engine will only scan a subset of the chunk if a detector matches the chunk.
- // If this flag is set to true, the engine will scan the entire chunk.
- scanEntireChunk bool
-
- // ahoCorasickHandler manages the Aho-Corasick trie and related keyword lookups.
- AhoCorasickCore *ahocorasick.Core
-
- // Engine synchronization primitives.
- sourceManager *sources.SourceManager
- results chan detectors.ResultWithMetadata
- detectableChunksChan chan detectableChunk
- verificationOverlapChunksChan chan verificationOverlapChunk
- workersWg sync.WaitGroup
- verificationOverlapWg sync.WaitGroup
- wgDetectorWorkers sync.WaitGroup
- WgNotifier sync.WaitGroup
-
- // Runtime information.
- metrics runtimeMetrics
- // numFoundResults is used to keep track of the number of results found.
- numFoundResults uint32
-
- // ResultsDispatcher is used to send results.
- dispatcher ResultsDispatcher
-
- // dedupeCache is used to deduplicate results by comparing the
- // detector type, raw result, and source metadata
- dedupeCache *lru.Cache[string, detectorspb.DecoderType]
-
- // verify determines whether the scanner will attempt to verify candidate secrets.
- verify bool
-
- // Note: bad hack only used for testing.
- verificationOverlapTracker *verificationOverlapTracker
-
- // detectorWorkerMultiplier is used to calculate the number of detector workers.
- detectorWorkerMultiplier int
- // notificationWorkerMultiplier is used to calculate the number of notification workers.
- notificationWorkerMultiplier int
- // verificationOverlapWorkerMultiplier is used to calculate the number of verification overlap workers.
- verificationOverlapWorkerMultiplier int
-}
-
-// NewEngine creates a new Engine instance with the provided configuration.
-func NewEngine(ctx context.Context, cfg *Config) (*Engine, error) {
- verificationCache := verificationcache.New(cfg.VerificationResultCache, cfg.VerificationCacheMetrics)
-
- engine := &Engine{
- concurrency: cfg.Concurrency,
- decoders: cfg.Decoders,
- detectors: cfg.Detectors,
- verificationCache: verificationCache,
- dispatcher: cfg.Dispatcher,
- verify: cfg.Verify,
- filterUnverified: cfg.FilterUnverified,
- filterEntropy: cfg.FilterEntropy,
- printAvgDetectorTime: cfg.PrintAvgDetectorTime,
- retainFalsePositives: cfg.LogFilteredUnverified,
- verificationOverlap: cfg.VerificationOverlap,
- sourceManager: cfg.SourceManager,
- scanEntireChunk: cfg.ShouldScanEntireChunk,
- detectorVerificationOverrides: cfg.DetectorVerificationOverrides,
- detectorWorkerMultiplier: cfg.DetectorWorkerMultiplier,
- notificationWorkerMultiplier: cfg.NotificationWorkerMultiplier,
- verificationOverlapWorkerMultiplier: cfg.VerificationOverlapWorkerMultiplier,
- }
- if engine.sourceManager == nil {
- return nil, fmt.Errorf("source manager is required")
- }
-
- engine.setDefaults(ctx)
-
- // Build include and exclude detector sets for filtering on engine initialization.
- includeDetectorSet, excludeDetectorSet, err := buildDetectorSets(cfg)
- if err != nil {
- return nil, err
- }
-
- // Apply include/exclude filters.
- var filters []func(detectors.Detector) bool
-
- if len(includeDetectorSet) > 0 {
- filters = append(filters, func(d detectors.Detector) bool {
- _, ok := getWithDetectorID(d, includeDetectorSet)
- return ok
- })
- }
-
- if len(excludeDetectorSet) > 0 {
- filters = append(filters, func(d detectors.Detector) bool {
- _, ok := getWithDetectorID(d, excludeDetectorSet)
- return !ok
- })
- }
-
- // Apply custom verifier endpoints to detectors that support it.
- detectorsWithCustomVerifierEndpoints, err := parseCustomVerifierEndpoints(cfg.VerifierEndpoints)
- if err != nil {
- return nil, err
- }
- if len(detectorsWithCustomVerifierEndpoints) > 0 {
- filters = append(filters, func(d detectors.Detector) bool {
- urls, ok := getWithDetectorID(d, detectorsWithCustomVerifierEndpoints)
- if !ok {
- return true
- }
- customizer, ok := d.(detectors.EndpointCustomizer)
- if !ok {
- return false
- }
-
- if cfg.CustomVerifiersOnly && len(urls) > 0 {
- customizer.UseCloudEndpoint(false)
- customizer.UseFoundEndpoints(false)
- }
-
- if err := customizer.SetConfiguredEndpoints(urls...); err != nil {
- return false
- }
-
- return true
- })
- }
- engine.applyFilters(filters...)
-
- if results := cfg.Results; len(results) > 0 {
- _, ok := results["verified"]
- engine.notifyVerifiedResults = ok
-
- _, ok = results["unknown"]
- engine.notifyUnknownResults = ok
-
- _, ok = results["unverified"]
- engine.notifyUnverifiedResults = ok
-
- if _, ok = results["filtered_unverified"]; ok {
- engine.retainFalsePositives = ok
- engine.notifyUnverifiedResults = ok
- }
- }
-
- if err := engine.initialize(ctx); err != nil {
- return nil, err
- }
-
- return engine, nil
-}
-
-// SetDetectorTimeout sets the maximum timeout for each detector to scan a chunk.
-func SetDetectorTimeout(timeout time.Duration) { detectionTimeout = timeout }
-
-// setDefaults ensures that if specific engine properties aren't provided,
-// they're set to reasonable default values. It makes the engine robust to
-// incomplete configuration.
-func (e *Engine) setDefaults(ctx context.Context) {
- if e.concurrency == 0 {
- numCPU := runtime.NumCPU()
- ctx.Logger().Info("No concurrency specified, defaulting to max", "cpu", numCPU)
- e.concurrency = numCPU
- }
-
- if e.detectorWorkerMultiplier < 1 {
- // bound by net i/o so it's higher than other workers
- e.detectorWorkerMultiplier = 8
- }
-
- if e.notificationWorkerMultiplier < 1 {
- e.notificationWorkerMultiplier = 1
- }
-
- if e.verificationOverlapWorkerMultiplier < 1 {
- e.verificationOverlapWorkerMultiplier = 1
- }
-
- // Default decoders handle common encoding formats.
- if len(e.decoders) == 0 {
- e.decoders = decoders.DefaultDecoders()
- }
-
- // Only use the default detectors if none are provided.
- if len(e.detectors) == 0 {
- e.detectors = defaults.DefaultDetectors()
- }
-
- if e.dispatcher == nil {
- e.dispatcher = NewPrinterDispatcher(new(output.PlainPrinter))
- }
- e.notifyVerifiedResults = true
- e.notifyUnverifiedResults = true
- e.notifyUnknownResults = true
-
- ctx.Logger().V(4).Info("default engine options set")
-}
-
-func buildDetectorSets(cfg *Config) (map[config.DetectorID]struct{}, map[config.DetectorID]struct{}, error) {
- includeList, err := config.ParseDetectors(cfg.IncludeDetectors)
- if err != nil {
- return nil, nil, fmt.Errorf("invalid include list detector configuration: %w", err)
- }
- excludeList, err := config.ParseDetectors(cfg.ExcludeDetectors)
- if err != nil {
- return nil, nil, fmt.Errorf("invalid exclude list detector configuration: %w", err)
- }
-
- includeDetectorSet := detectorTypeToSet(includeList)
- excludeDetectorSet := detectorTypeToSet(excludeList)
-
- // Verify that all the user-provided detectors support the optional
- // detector features.
- if id, err := verifyDetectorsAreVersioner(includeDetectorSet); err != nil {
- return nil, nil, fmt.Errorf("invalid include list detector configuration id %v: %w", id, err)
- }
-
- if id, err := verifyDetectorsAreVersioner(excludeDetectorSet); err != nil {
- return nil, nil, fmt.Errorf("invalid exclude list detector configuration id %v: %w", id, err)
- }
-
- return includeDetectorSet, excludeDetectorSet, nil
-}
-
-func parseCustomVerifierEndpoints(endpoints map[string]string) (map[config.DetectorID][]string, error) {
- if len(endpoints) == 0 {
- return nil, nil
- }
-
- customVerifierEndpoints, err := config.ParseVerifierEndpoints(endpoints)
- if err != nil {
- return nil, fmt.Errorf("invalid verifier detector configuration: %w", err)
- }
-
- if id, err := verifyDetectorsAreVersioner(customVerifierEndpoints); err != nil {
- return nil, fmt.Errorf("invalid verifier detector configuration id %v: %w", id, err)
- }
- // Extra check for endpoint customization.
- isEndpointCustomizer := defaults.DefaultDetectorTypesImplementing[detectors.EndpointCustomizer]()
- for id := range customVerifierEndpoints {
- if _, ok := isEndpointCustomizer[id.ID]; !ok {
- return nil, fmt.Errorf("endpoint provided but detector does not support endpoint customization: %w", err)
- }
- }
- return customVerifierEndpoints, nil
-}
-
-// detectorTypeToSet is a helper function to convert a slice of detector IDs into a set.
-func detectorTypeToSet(detectors []config.DetectorID) map[config.DetectorID]struct{} {
- out := make(map[config.DetectorID]struct{}, len(detectors))
- for _, d := range detectors {
- out[d] = struct{}{}
- }
- return out
-}
-
-// getWithDetectorID is a helper function to get a value from a map using a
-// detector's ID. This function behaves like a normal map lookup, with an extra
-// step of checking for the non-specific version of a detector.
-func getWithDetectorID[T any](d detectors.Detector, data map[config.DetectorID]T) (T, bool) {
- key := config.GetDetectorID(d)
- // Check if the specific ID is provided.
- if t, ok := data[key]; ok || key.Version == 0 {
- return t, ok
- }
- // Check if the generic type is provided without a version.
- // This means "all" versions of a type.
- key.Version = 0
- t, ok := data[key]
- return t, ok
-}
-
-// verifyDetectorsAreVersioner checks all keys in a provided map to verify the
-// provided type is actually a Versioner.
-func verifyDetectorsAreVersioner[T any](data map[config.DetectorID]T) (config.DetectorID, error) {
- isVersioner := defaults.DefaultDetectorTypesImplementing[detectors.Versioner]()
- for id := range data {
- if id.Version == 0 {
- // Version not provided.
- continue
- }
- if _, ok := isVersioner[id.ID]; ok {
- // Version provided for a Versioner detector.
- continue
- }
- // Version provided on a non-Versioner detector.
- return id, fmt.Errorf("version provided but detector does not have a version")
- }
- return config.DetectorID{}, nil
-}
-
-// applyFilters applies a variable number of filters to the detectors.
-func (e *Engine) applyFilters(filters ...func(detectors.Detector) bool) {
- for _, filter := range filters {
- e.detectors = filterDetectors(filter, e.detectors)
- }
-}
-
-func filterDetectors(filterFunc func(detectors.Detector) bool, input []detectors.Detector) []detectors.Detector {
- var out []detectors.Detector
- for _, detector := range input {
- if filterFunc(detector) {
- out = append(out, detector)
- }
- }
- return out
-}
-
-// initialize prepares the engine's internal structures. The LRU cache optimizes
-// deduplication efforts, allowing the engine to quickly check if a chunk has
-// been processed before, thereby saving computational overhead.
-func (e *Engine) initialize(ctx context.Context) error {
- // TODO (ahrav): Determine the optimal cache size.
- const cacheSize = 512 // number of entries in the LRU cache
-
- cache, err := lru.New[string, detectorspb.DecoderType](cacheSize)
- if err != nil {
- return fmt.Errorf("failed to initialize LRU cache: %w", err)
- }
- const (
- // detectableChunksChanMultiplier is set to accommodate a high number of concurrent worker goroutines.
- // This multiplier ensures that the detectableChunksChan channel has sufficient buffer capacity
- // to hold messages from multiple worker groups (detector workers/ verificationOverlap workers) without blocking.
- // A large buffer helps accommodate for the fact workers are producing data at a faster rate
- // than it can be consumed.
- detectableChunksChanMultiplier = 50
- // verificationOverlapChunksChanMultiplier uses a smaller buffer compared to detectableChunksChanMultiplier.
- // This reflects the anticipated lower volume of data that needs re-verification.
- // The buffer size is a trade-off between memory usage and the need to prevent blocking.
- verificationOverlapChunksChanMultiplier = 25
- resultsChanMultiplier = detectableChunksChanMultiplier
- )
-
- // Channels are used for communication between different parts of the engine,
- // ensuring that data flows smoothly without race conditions.
- // The buffer sizes for these channels are set to multiples of defaultChannelBuffer,
- // considering the expected concurrency and workload in the system.
- e.detectableChunksChan = make(chan detectableChunk, defaultChannelBuffer*detectableChunksChanMultiplier)
- e.verificationOverlapChunksChan = make(
- chan verificationOverlapChunk, defaultChannelBuffer*verificationOverlapChunksChanMultiplier,
- )
- e.results = make(chan detectors.ResultWithMetadata, defaultChannelBuffer*resultsChanMultiplier)
- e.dedupeCache = cache
- ctx.Logger().V(4).Info("engine initialized")
-
- // Configure the EntireChunkSpanCalculator if the engine is set to scan the entire chunk.
- var ahoCOptions []ahocorasick.CoreOption
- if e.scanEntireChunk {
- ahoCOptions = append(ahoCOptions, ahocorasick.WithSpanCalculator(new(ahocorasick.EntireChunkSpanCalculator)))
- }
-
- ctx.Logger().V(4).Info("setting up aho-corasick core")
- e.AhoCorasickCore = ahocorasick.NewAhoCorasickCore(e.detectors, ahoCOptions...)
- ctx.Logger().V(4).Info("set up aho-corasick core")
-
- return nil
-}
-
-type verificationOverlapTracker struct {
- verificationOverlapDuplicateCount int
- mu sync.Mutex
-}
-
-func (r *verificationOverlapTracker) increment() {
- r.mu.Lock()
- r.verificationOverlapDuplicateCount++
- r.mu.Unlock()
-}
-
-const ignoreTag = "trufflehog:ignore"
-
-// AhoCorasickCoreKeywords returns a set of keywords that the engine's
-// AhoCorasickCore is using.
-func (e *Engine) AhoCorasickCoreKeywords() map[string]struct{} {
- // Turn AhoCorasick keywordsToDetectors into a map of keywords
- keywords := make(map[string]struct{})
- for key := range e.AhoCorasickCore.KeywordsToDetectors() {
- keywords[key] = struct{}{}
- }
- return keywords
-}
-
-// HasFoundResults returns true if any results are found.
-func (e *Engine) HasFoundResults() bool {
- return atomic.LoadUint32(&e.numFoundResults) > 0
-}
-
-// GetMetrics returns a copy of Metrics.
-// It's safe for concurrent use, and the caller can't modify the original data.
-func (e *Engine) GetMetrics() Metrics {
- e.metrics.mu.RLock()
- defer e.metrics.mu.RUnlock()
-
- result := e.metrics.Metrics
- result.AvgDetectorTime = make(map[string]time.Duration, len(e.metrics.AvgDetectorTime))
-
- for detectorName, durations := range e.DetectorAvgTime() {
- var total time.Duration
- for _, d := range durations {
- total += d
- }
- avgDuration := total / time.Duration(len(durations))
- result.AvgDetectorTime[detectorName] = avgDuration
- }
-
- result.ScanDuration = e.metrics.getScanDuration()
-
- return result
-}
-
-// GetDetectorsMetrics returns a copy of the average time taken by each detector.
-func (e *Engine) GetDetectorsMetrics() map[string]time.Duration {
- e.metrics.mu.RLock()
- defer e.metrics.mu.RUnlock()
-
- result := make(map[string]time.Duration, len(defaults.DefaultDetectors()))
- for detectorName, durations := range e.DetectorAvgTime() {
- var total time.Duration
- for _, d := range durations {
- total += d
- }
- avgDuration := total / time.Duration(len(durations))
- result[detectorName] = avgDuration
- }
-
- return result
-}
-
-// DetectorAvgTime returns the average time taken by each detector.
-func (e *Engine) DetectorAvgTime() map[string][]time.Duration {
- logger := context.Background().Logger()
- avgTime := map[string][]time.Duration{}
- e.metrics.detectorAvgTime.Range(func(k, v any) bool {
- key, ok := k.(string)
- if !ok {
- logger.Info("expected detectorAvgTime key to be a string")
- return true
- }
-
- value, ok := v.([]time.Duration)
- if !ok {
- logger.Info("expected detectorAvgTime value to be []time.Duration")
- return true
- }
- avgTime[key] = value
- return true
- })
- return avgTime
-}
-
-// Start initializes and activates the engine's processing pipeline.
-// It sets up various default configurations, prepares lookup structures for
-// detectors, and kickstarts all necessary workers. Once started, the engine
-// begins processing input data to identify secrets.
-func (e *Engine) Start(ctx context.Context) {
- e.metrics = runtimeMetrics{Metrics: Metrics{scanStartTime: time.Now()}}
- e.sanityChecks(ctx)
- e.startWorkers(ctx)
-}
-
-var defaultChannelBuffer = runtime.NumCPU()
-
-// Sanity check detectors for duplicate configuration. Only log in case
-// a detector has been configured in a way that isn't represented by
-// the DetectorID (type and version).
-func (e *Engine) sanityChecks(ctx context.Context) {
- seenDetectors := make(map[config.DetectorID]struct{}, len(e.detectors))
- for _, det := range e.detectors {
- id := config.GetDetectorID(det)
- if _, ok := seenDetectors[id]; ok && id.ID != detectorspb.DetectorType_CustomRegex {
- ctx.Logger().Info("possible duplicate detector configured", "detector", id)
- }
- seenDetectors[id] = struct{}{}
- }
-}
-
-// startWorkers initiates all necessary workers. Workers handle processing of
-// chunks concurrently. Separating the initialization of different types of
-// workers helps in scalability and makes it easier to diagnose issues.
-func (e *Engine) startWorkers(ctx context.Context) {
- // Scanner workers process input data and extract chunks for detectors.
- e.startScannerWorkers(ctx)
-
- // Detector workers apply keyword matching, regexes and API calls to detect secrets in chunks.
- e.startDetectorWorkers(ctx)
-
- // verificationOverlap workers handle verification of chunks that have been detected by multiple detectors.
- // They ensure that verification is disabled for any secrets that have been detected by multiple detectors.
- e.startVerificationOverlapWorkers(ctx)
-
- // ResultsDispatcher workers communicate detected issues to the user or any downstream systems.
- // We want 1/4th of the notifier workers as the number of scanner workers.
- e.startNotifierWorkers(ctx)
-}
-
-func (e *Engine) startScannerWorkers(ctx context.Context) {
- ctx.Logger().V(2).Info("starting scanner workers", "count", e.concurrency)
- for worker := uint64(0); worker < uint64(e.concurrency); worker++ {
- e.workersWg.Add(1)
- go func() {
- ctx := context.WithValue(ctx, "scanner_worker_id", common.RandomID(5))
- defer common.Recover(ctx)
- defer e.workersWg.Done()
- e.scannerWorker(ctx)
- }()
- }
-}
-
-func (e *Engine) startDetectorWorkers(ctx context.Context) {
- numWorkers := e.concurrency * e.detectorWorkerMultiplier
-
- ctx.Logger().V(2).Info("starting detector workers", "count", numWorkers)
- for worker := 0; worker < numWorkers; worker++ {
- e.wgDetectorWorkers.Add(1)
- go func() {
- ctx := context.WithValue(ctx, "detector_worker_id", common.RandomID(5))
- defer common.Recover(ctx)
- defer e.wgDetectorWorkers.Done()
- e.detectorWorker(ctx)
- }()
- }
-}
-
-func (e *Engine) startVerificationOverlapWorkers(ctx context.Context) {
- numWorkers := e.concurrency * e.verificationOverlapWorkerMultiplier
-
- ctx.Logger().V(2).Info("starting verificationOverlap workers", "count", numWorkers)
- for worker := 0; worker < numWorkers; worker++ {
- e.verificationOverlapWg.Add(1)
- go func() {
- ctx := context.WithValue(ctx, "verification_overlap_worker_id", common.RandomID(5))
- defer common.Recover(ctx)
- defer e.verificationOverlapWg.Done()
- e.verificationOverlapWorker(ctx)
- }()
- }
-}
-
-func (e *Engine) startNotifierWorkers(ctx context.Context) {
- numWorkers := e.notificationWorkerMultiplier * e.concurrency
-
- ctx.Logger().V(2).Info("starting notifier workers", "count", numWorkers)
- for worker := 0; worker < numWorkers; worker++ {
- e.WgNotifier.Add(1)
- go func() {
- ctx := context.WithValue(ctx, "notifier_worker_id", common.RandomID(5))
- defer common.Recover(ctx)
- defer e.WgNotifier.Done()
- e.notifierWorker(ctx)
- }()
- }
-}
-
-// Finish waits for running sources to complete and workers to finish scanning
-// chunks before closing their respective channels. Once Finish is called, no
-// more sources may be scanned by the engine.
-func (e *Engine) Finish(ctx context.Context) error {
- defer common.RecoverWithExit(ctx)
- // Wait for the sources to finish putting chunks onto the chunks channel.
- err := e.sourceManager.Wait()
-
- e.workersWg.Wait() // Wait for the workers to finish scanning chunks.
-
- close(e.verificationOverlapChunksChan)
- e.verificationOverlapWg.Wait()
-
- close(e.detectableChunksChan)
- e.wgDetectorWorkers.Wait() // Wait for the detector workers to finish detecting chunks.
-
- 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.
-
- e.metrics.ScanDuration = time.Since(e.metrics.scanStartTime)
-
- return err
-}
-
-func (e *Engine) ChunksChan() <-chan *sources.Chunk {
- return e.sourceManager.Chunks()
-}
-
-func (e *Engine) ResultsChan() chan detectors.ResultWithMetadata {
- return e.results
-}
-
-// ScanChunk injects a chunk into the output stream of chunks to be scanned.
-// This method should rarely be used. TODO(THOG-1577): Remove when dependencies
-// no longer rely on this functionality.
-func (e *Engine) ScanChunk(chunk *sources.Chunk) {
- e.sourceManager.ScanChunk(chunk)
-}
-
-// detectableChunk is a decoded chunk that is ready to be scanned by its detector.
-type detectableChunk struct {
- detector *ahocorasick.DetectorMatch
- chunk sources.Chunk
- decoder detectorspb.DecoderType
- wgDoneFn func()
-}
-
-// verificationOverlapChunk is a decoded chunk that has multiple detectors that match it.
-// It will be initially processed with verification disabled, and then reprocessed with verification
-// enabled if the same secret was not found by multiple detectors.
-type verificationOverlapChunk struct {
- chunk sources.Chunk
- decoder detectorspb.DecoderType
- detectors []*ahocorasick.DetectorMatch
- verificationOverlapWgDoneFn func()
-}
-
-func (e *Engine) scannerWorker(ctx context.Context) {
- var wgDetect sync.WaitGroup
- var wgVerificationOverlap sync.WaitGroup
-
- for chunk := range e.ChunksChan() {
- startTime := time.Now()
- sourceVerify := chunk.Verify
- for _, decoder := range e.decoders {
- decodeStart := time.Now()
- // This copy is needed to preserve the original chunk.Data across multiple decoders.
- chunkCopy := *chunk
- decoded := decoder.FromChunk(&chunkCopy)
- decodeTime := time.Since(decodeStart).Microseconds()
- decodeLatency.WithLabelValues(decoder.Type().String(), chunk.SourceName).Observe(float64(decodeTime))
-
- if decoded == nil {
- // This means that the decoder didn't understand this chunk and isn't applicable to it.
- continue
- }
-
- matchingDetectors := e.AhoCorasickCore.FindDetectorMatches(decoded.Chunk.Data)
- if len(matchingDetectors) > 1 && !e.verificationOverlap {
- wgVerificationOverlap.Add(1)
- e.verificationOverlapChunksChan <- verificationOverlapChunk{
- chunk: *decoded.Chunk,
- detectors: matchingDetectors,
- decoder: decoded.DecoderType,
- verificationOverlapWgDoneFn: wgVerificationOverlap.Done,
- }
- continue
- }
-
- for _, detector := range matchingDetectors {
- decoded.Chunk.Verify = e.shouldVerifyChunk(sourceVerify, detector, e.detectorVerificationOverrides)
- wgDetect.Add(1)
- e.detectableChunksChan <- detectableChunk{
- chunk: *decoded.Chunk,
- detector: detector,
- decoder: decoded.DecoderType,
- wgDoneFn: wgDetect.Done,
- }
- }
- }
-
- dataSize := float64(len(chunk.Data))
-
- scanBytesPerChunk.WithLabelValues(chunk.SourceType.String()).Observe(dataSize)
- jobBytesScanned.WithLabelValues(
- chunk.SourceType.String(),
- chunk.SourceName,
- ).Add(dataSize)
- chunksScannedLatency.Observe(float64(time.Since(startTime).Microseconds()))
- jobChunksScanned.WithLabelValues(
- chunk.SourceType.String(),
- chunk.SourceName,
- ).Inc()
-
- atomic.AddUint64(&e.metrics.ChunksScanned, 1)
- atomic.AddUint64(&e.metrics.BytesScanned, uint64(dataSize))
- }
-
- wgVerificationOverlap.Wait()
- wgDetect.Wait()
- ctx.Logger().V(4).Info("finished scanning chunks")
-}
-
-func (e *Engine) shouldVerifyChunk(
- sourceVerify bool,
- detector detectors.Detector,
- detectorVerificationOverrides map[config.DetectorID]bool,
-) bool {
- // The verify flag takes precedence over the detector's verification flag.
- if !e.verify {
- return false
- }
-
- detectorId := config.DetectorID{ID: detector.Type(), Version: 0}
-
- if v, ok := detector.(detectors.Versioner); ok {
- detectorId.Version = v.Version()
- }
-
- if detectorVerify, ok := detectorVerificationOverrides[detectorId]; ok {
- return detectorVerify
- }
-
- // If the user is running with a detector verification override that does not specify a particular detector version,
- // then its override map entry will have version 0. We should check for that too, but if the detector being checked
- // doesn't have any version information then its version is 0, so we've already done the check, and we don't need to
- // do it a second time.
- if detectorId.Version != 0 {
- detectorId.Version = 0
-
- if detectorVerify, ok := detectorVerificationOverrides[detectorId]; ok {
- return detectorVerify
- }
- }
-
- return sourceVerify
-}
-
-// chunkSecretKey ties secrets to the specific detector that found them. This allows identifying identical
-// credentials extracted by multiple different detectors processing the same chunk. Or duplicates found
-// by the same detector in the chunk. Exact matches on lookup indicate a duplicate secret for a detector
-// in that chunk - which is expected and not malicious. Those intra-detector dupes are still verified.
-type chunkSecretKey struct {
- secret string
- detectorKey ahocorasick.DetectorKey
-}
-
-func likelyDuplicate(ctx context.Context, val chunkSecretKey, dupes map[chunkSecretKey]struct{}) bool {
- const similarityThreshold = 0.9
-
- valStr := val.secret
- for dupeKey := range dupes {
- dupe := dupeKey.secret
- // Avoid comparing strings of vastly different lengths.
- if len(dupe)*10 < len(valStr)*9 || len(dupe)*10 > len(valStr)*11 {
- continue
- }
-
- // If the detector type is the same, we don't need to compare the strings.
- // These are not duplicates, and should be verified.
- if val.detectorKey.Type() == dupeKey.detectorKey.Type() {
- continue
- }
-
- if valStr == dupe {
- ctx.Logger().V(2).Info(
- "found exact duplicate",
- )
- return true
- }
-
- similarity := strutil.Similarity(valStr, dupe, metrics.NewLevenshtein())
-
- // close enough
- if similarity > similarityThreshold {
- ctx.Logger().V(2).Info(
- "found similar duplicate",
- )
- return true
- }
- }
- return false
-}
-
-func (e *Engine) verificationOverlapWorker(ctx context.Context) {
- var wgDetect sync.WaitGroup
-
- // Reuse the same map and slice to avoid allocations.
- const avgSecretsPerDetector = 8
- detectorKeysWithResults := make(map[ahocorasick.DetectorKey]*ahocorasick.DetectorMatch, avgSecretsPerDetector)
- chunkSecrets := make(map[chunkSecretKey]struct{}, avgSecretsPerDetector)
-
- for chunk := range e.verificationOverlapChunksChan {
- for _, detector := range chunk.detectors {
- isFalsePositive := detectors.GetFalsePositiveCheck(detector.Detector)
-
- // DO NOT VERIFY at this stage of the pipeline.
- matchedBytes := detector.Matches()
- for _, match := range matchedBytes {
- ctx, cancel := context.WithTimeout(ctx, time.Second*2)
- results, err := detector.FromData(ctx, false, match)
- cancel()
- if err != nil {
- ctx.Logger().Error(
- err, "error finding results in chunk during verification overlap",
- "detector", detector.Key.Type().String(),
- )
- }
-
- if len(results) == 0 {
- continue
- }
- if _, ok := detectorKeysWithResults[detector.Key]; !ok {
- detectorKeysWithResults[detector.Key] = detector
- }
-
- // If results filtration eliminates a rotated secret, then that rotation will never be reported. This
- // problem can theoretically occur for any scan, but we've only actually seen it in practice during
- // targeted scans. (The reason for this discrepancy is unclear.) The simplest fix is therefore to
- // disable filtration for targeted scans, but if you're here because this problem surfaced for a
- // non-targeted scan then we'll have to solve it correctly.
- if chunk.chunk.SecretID == 0 {
- results = e.filterResults(ctx, detector, results)
- }
-
- for _, res := range results {
- var val []byte
- if res.RawV2 != nil {
- val = res.RawV2
- } else {
- val = res.Raw
- }
-
- // Use levenstein distance to determine if the secret is likely the same.
- // Ex:
- // - postman api key: PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r
- // - malicious detector "api key": qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r
- key := chunkSecretKey{secret: string(val), detectorKey: detector.Key}
- if _, ok := chunkSecrets[key]; ok {
- continue
- }
-
- if likelyDuplicate(ctx, key, chunkSecrets) {
- // This indicates that the same secret was found by multiple detectors.
- // We should NOT VERIFY this chunk's data.
- if e.verificationOverlapTracker != nil {
- e.verificationOverlapTracker.increment()
- }
- res.SetVerificationError(errOverlap)
- e.processResult(
- ctx,
- detectableChunk{
- chunk: chunk.chunk,
- detector: detector,
- decoder: chunk.decoder,
- wgDoneFn: wgDetect.Done,
- },
- res,
- isFalsePositive,
- )
-
- // Remove the detector key from the list of detector keys with results.
- // This is to ensure that the chunk is not reprocessed with verification enabled
- // for this detector.
- delete(detectorKeysWithResults, detector.Key)
- }
- chunkSecrets[key] = struct{}{}
- }
- }
- }
-
- for _, detector := range detectorKeysWithResults {
- wgDetect.Add(1)
- chunk.chunk.Verify = e.shouldVerifyChunk(chunk.chunk.Verify, detector, e.detectorVerificationOverrides)
- e.detectableChunksChan <- detectableChunk{
- chunk: chunk.chunk,
- detector: detector,
- decoder: chunk.decoder,
- wgDoneFn: wgDetect.Done,
- }
- }
-
- // Empty the dupes and detectors slice
- for k := range chunkSecrets {
- delete(chunkSecrets, k)
- }
- for k := range detectorKeysWithResults {
- delete(detectorKeysWithResults, k)
- }
-
- chunk.verificationOverlapWgDoneFn()
- }
-
- wgDetect.Wait()
-}
-
-func (e *Engine) detectorWorker(ctx context.Context) {
- for data := range e.detectableChunksChan {
- start := time.Now()
- e.detectChunk(ctx, data)
- chunksDetectedLatency.Observe(float64(time.Since(start).Milliseconds()))
- }
-}
-
-func (e *Engine) detectChunk(ctx context.Context, data detectableChunk) {
- var start time.Time
- if e.printAvgDetectorTime {
- start = time.Now()
- }
- defer common.Recover(ctx)
-
- ctx = context.WithValues(ctx,
- "detector", data.detector.Key.Loggable(),
- "decoder_type", data.decoder.String(),
- "chunk_source_name", data.chunk.SourceName,
- "chunk_source_id", data.chunk.SourceID,
- "chunk_source_metadata", data.chunk.SourceMetadata.String())
-
- ctx.Logger().V(5).Info("Starting to detect chunk")
-
- isFalsePositive := detectors.GetFalsePositiveCheck(data.detector.Detector)
-
- var matchCount int
- // To reduce the overhead of regex calls in the detector,
- // we limit the amount of data passed to each detector.
- // The matches field of the DetectorMatch struct contains the
- // relevant portions of the chunk data that were matched.
- // This avoids the need for additional regex processing on the entire chunk data.
- matches := data.detector.Matches()
- for _, matchBytes := range matches {
- matchCount++
- detectBytesPerMatch.Observe(float64(len(matchBytes)))
-
- ctx, cancel := context.WithTimeout(ctx, detectionTimeout)
- t := time.AfterFunc(detectionTimeout+1*time.Second, func() {
- ctx.Logger().Error(nil, "a detector ignored the context timeout")
- })
- results, err := e.verificationCache.FromData(
- ctx,
- data.detector.Detector,
- data.chunk.Verify,
- data.chunk.SecretID != 0,
- matchBytes)
- t.Stop()
- cancel()
- if err != nil {
- ctx.Logger().Error(err, "error finding results in chunk")
- continue
- }
-
- detectorExecutionCount.WithLabelValues(
- data.detector.Type().String(),
- strconv.Itoa(int(data.chunk.JobID)),
- data.chunk.SourceName,
- ).Inc()
- detectorExecutionDuration.WithLabelValues(
- data.detector.Type().String(),
- ).Observe(float64(time.Since(start).Milliseconds()))
-
- if e.printAvgDetectorTime && len(results) > 0 {
- elapsed := time.Since(start)
- detectorName := results[0].DetectorType.String()
- avgTimeI, ok := e.metrics.detectorAvgTime.Load(detectorName)
- var avgTime []time.Duration
- if ok {
- avgTime, ok = avgTimeI.([]time.Duration)
- if !ok {
- return
- }
- }
- avgTime = append(avgTime, elapsed)
- e.metrics.detectorAvgTime.Store(detectorName, avgTime)
- }
-
- // If results filtration eliminates a rotated secret, then that rotation will never be reported. This problem
- // can theoretically occur for any scan, but we've only actually seen it in practice during targeted scans. (The
- // reason for this discrepancy is unclear.) The simplest fix is therefore to disable filtration for targeted
- // scans, but if you're here because this problem surfaced for a non-targeted scan then we'll have to solve it
- // correctly.
- if data.chunk.SecretID == 0 {
- results = e.filterResults(ctx, data.detector, results)
- }
-
- for _, res := range results {
- e.processResult(ctx, data, res, isFalsePositive)
- }
- }
-
- matchesPerChunk.Observe(float64(matchCount))
-
- ctx.Logger().V(5).Info("Finished detecting chunk")
-
- data.wgDoneFn()
-}
-
-func (e *Engine) filterResults(
- ctx context.Context,
- detector *ahocorasick.DetectorMatch,
- results []detectors.Result,
-) []detectors.Result {
- clean := detectors.CleanResults
- ignoreConfig := false
- if cleaner, ok := detector.Detector.(detectors.CustomResultsCleaner); ok {
- clean = cleaner.CleanResults
- ignoreConfig = cleaner.ShouldCleanResultsIrrespectiveOfConfiguration()
- }
- if e.filterUnverified || ignoreConfig {
- results = clean(results)
- }
-
- if !e.retainFalsePositives {
- results = detectors.FilterKnownFalsePositives(ctx, detector.Detector, results)
- }
-
- if e.filterEntropy != 0 {
- results = detectors.FilterResultsWithEntropy(ctx, results, e.filterEntropy, e.retainFalsePositives)
- }
-
- return results
-}
-
-// processResult generates a detectors.ResultWithMetadata from the provided chunk and result and puts it on the results
-// channel, unless the result exists on a line with an ignore tag, in which case no result is generated.
-//
-// CMR: The provided chunk is wrapped in a detectableChunk, but I'm pretty sure that's purely out of convenience
-// (because that's what this function's callers are using when they call this function). We're past detection at this
-// point in the engine, so we should probably refactor that parameter into a less confusing data type.
-func (e *Engine) processResult(
- ctx context.Context,
- data detectableChunk,
- res detectors.Result,
- isFalsePositive func(detectors.Result) (bool, string),
-) {
- ignoreLinePresent := false
- if SupportsLineNumbers(data.chunk.SourceType) {
- copyChunk := data.chunk
- copyMetaDataClone := proto.Clone(data.chunk.SourceMetadata)
- if copyMetaData, ok := copyMetaDataClone.(*source_metadatapb.MetaData); ok {
- copyChunk.SourceMetadata = copyMetaData
- }
- fragStart, mdLine, link := FragmentFirstLineAndLink(©Chunk)
- ignoreLinePresent = SetResultLineNumber(©Chunk, &res, fragStart, mdLine)
- if err := UpdateLink(ctx, copyChunk.SourceMetadata, link, *mdLine); err != nil {
- ctx.Logger().Error(err, "error setting link")
- return
- }
- data.chunk = copyChunk
- }
- if ignoreLinePresent {
- return
- }
-
- secret := detectors.CopyMetadata(&data.chunk, res)
- secret.DecoderType = data.decoder
- secret.DetectorDescription = data.detector.Detector.Description()
-
- if !res.Verified && res.Raw != nil {
- isFp, _ := isFalsePositive(res)
- secret.IsWordlistFalsePositive = isFp
- }
-
- e.results <- secret
-}
-
-func (e *Engine) notifierWorker(ctx context.Context) {
- for result := range e.ResultsChan() {
- startTime := time.Now()
- // Filter unwanted results, based on `--results`.
- if !result.Verified {
- if result.VerificationError() != nil {
- if !e.notifyUnknownResults {
- // Skip results with verification errors.
- continue
- }
- } else if !e.notifyUnverifiedResults {
- // Skip unverified results.
- continue
- }
- } else if !e.notifyVerifiedResults {
- // Skip verified results.
- // TODO: Is this a legitimate use case?
- continue
- }
- atomic.AddUint32(&e.numFoundResults, 1)
-
- // Dedupe results by comparing the detector type, raw result, and source metadata.
- // We want to avoid duplicate results with different decoder types, but we also
- // want to include duplicate results with the same decoder type.
- // Duplicate results with the same decoder type SHOULD have their own entry in the
- // results list, this would happen if the same secret is found multiple times.
- // Note: If the source type is postman, we dedupe the results regardless of decoder type.
- key := fmt.Sprintf("%s%s%s%+v", result.DetectorType.String(), result.Raw, result.RawV2, result.SourceMetadata)
- if val, ok := e.dedupeCache.Get(key); ok && (val != result.DecoderType ||
- result.SourceType == sourcespb.SourceType_SOURCE_TYPE_POSTMAN) {
- continue
- }
- e.dedupeCache.Add(key, result.DecoderType)
-
- if result.Verified {
- atomic.AddUint64(&e.metrics.VerifiedSecretsFound, 1)
- } else {
- atomic.AddUint64(&e.metrics.UnverifiedSecretsFound, 1)
- }
-
- if err := e.dispatcher.Dispatch(ctx, result); err != nil {
- ctx.Logger().Error(err, "error notifying result")
- }
-
- chunksNotifiedLatency.Observe(float64(time.Since(startTime).Milliseconds()))
- }
-}
-
-// SupportsLineNumbers determines if a line number can be found for a source type.
-func SupportsLineNumbers(sourceType sourcespb.SourceType) bool {
- switch sourceType {
- case sourcespb.SourceType_SOURCE_TYPE_GIT,
- sourcespb.SourceType_SOURCE_TYPE_GITHUB,
- sourcespb.SourceType_SOURCE_TYPE_GITHUB_REALTIME,
- sourcespb.SourceType_SOURCE_TYPE_GITLAB,
- sourcespb.SourceType_SOURCE_TYPE_BITBUCKET,
- sourcespb.SourceType_SOURCE_TYPE_GERRIT,
- sourcespb.SourceType_SOURCE_TYPE_GITHUB_UNAUTHENTICATED_ORG,
- sourcespb.SourceType_SOURCE_TYPE_PUBLIC_GIT,
- sourcespb.SourceType_SOURCE_TYPE_FILESYSTEM,
- sourcespb.SourceType_SOURCE_TYPE_AZURE_REPOS:
- return true
- default:
- return false
- }
-}
-
-// FragmentLineOffset sets the line number for a provided source chunk with a given detector result.
-func FragmentLineOffset(chunk *sources.Chunk, result *detectors.Result) (int64, bool) {
- // get the primary secret value from the result if set
- secret := result.GetPrimarySecretValue()
- if secret == "" {
- secret = string(result.Raw)
- }
-
- before, after, found := bytes.Cut(chunk.Data, []byte(secret))
- if !found {
- return 0, false
- }
- lineNumber := int64(bytes.Count(before, []byte("\n")))
- result.SetPrimarySecretLine(lineNumber)
- // If the line contains the ignore tag, we should ignore the result.
- endLine := bytes.Index(after, []byte("\n"))
- if endLine == -1 {
- endLine = len(after)
- }
- if bytes.Contains(after[:endLine], []byte(ignoreTag)) {
- return lineNumber, true
- }
- return lineNumber, false
-}
-
-// FragmentFirstLineAndLink extracts the first line number and the link from the chunk metadata.
-// It returns:
-// - The first line number of the fragment.
-// - A pointer to the line number, facilitating direct updates.
-// - The link associated with the fragment. This link may be updated in the chunk metadata
-// if there's a change in the line number.
-func FragmentFirstLineAndLink(chunk *sources.Chunk) (int64, *int64, string) {
- if chunk.SourceMetadata == nil {
- return 0, nil, ""
- }
-
- var (
- fragmentStart *int64
- link string
- )
- switch metadata := chunk.SourceMetadata.GetData().(type) {
- case *source_metadatapb.MetaData_Git:
- fragmentStart = &metadata.Git.Line
- case *source_metadatapb.MetaData_Github:
- fragmentStart = &metadata.Github.Line
- link = metadata.Github.Link
- case *source_metadatapb.MetaData_Gitlab:
- fragmentStart = &metadata.Gitlab.Line
- link = metadata.Gitlab.Link
- case *source_metadatapb.MetaData_Bitbucket:
- fragmentStart = &metadata.Bitbucket.Line
- link = metadata.Bitbucket.Link
- case *source_metadatapb.MetaData_Gerrit:
- fragmentStart = &metadata.Gerrit.Line
- case *source_metadatapb.MetaData_Filesystem:
- fragmentStart = &metadata.Filesystem.Line
- link = metadata.Filesystem.Link
- case *source_metadatapb.MetaData_AzureRepos:
- fragmentStart = &metadata.AzureRepos.Line
- link = metadata.AzureRepos.Link
- default:
- return 1, nil, ""
- }
-
- // Ensure we maintain 1-based line indexing if fragmentStart is not set or is 0.
- if *fragmentStart == 0 {
- *fragmentStart = 1
- }
-
- return *fragmentStart, fragmentStart, link
-}
-
-// SetResultLineNumber sets the line number in the provided result.
-func SetResultLineNumber(chunk *sources.Chunk, result *detectors.Result, fragStart int64, mdLine *int64) bool {
- offset, skip := FragmentLineOffset(chunk, result)
- *mdLine = fragStart + offset
- return skip
-}
-
-// UpdateLink updates the link of the provided source metadata.
-func UpdateLink(ctx context.Context, metadata *source_metadatapb.MetaData, link string, line int64) error {
- if metadata == nil {
- return fmt.Errorf("metadata is nil when setting the link")
- }
-
- if link == "" {
- ctx.Logger().V(4).Info("link is empty, skipping update")
- return nil
- }
-
- newLink := giturl.UpdateLinkLineNumber(ctx, link, line)
-
- switch meta := metadata.GetData().(type) {
- case *source_metadatapb.MetaData_Github:
- meta.Github.Link = newLink
- case *source_metadatapb.MetaData_Gitlab:
- meta.Gitlab.Link = newLink
- case *source_metadatapb.MetaData_Bitbucket:
- meta.Bitbucket.Link = newLink
- case *source_metadatapb.MetaData_Filesystem:
- meta.Filesystem.Link = newLink
- case *source_metadatapb.MetaData_AzureRepos:
- meta.AzureRepos.Link = newLink
- default:
- return fmt.Errorf("unsupported metadata type")
- }
- return nil
-}
diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go
deleted file mode 100644
index c5118e90d579..000000000000
--- a/pkg/engine/engine_test.go
+++ /dev/null
@@ -1,1734 +0,0 @@
-package engine
-
-import (
- aCtx "context"
- "fmt"
- "math/rand"
- "net/http"
- "net/http/httptest"
- "os"
- "path/filepath"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "google.golang.org/protobuf/testing/protocmp"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/config"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/custom_detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab/v2"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/verificationcache"
-)
-
-const fakeDetectorKeyword = "fakedetector"
-
-type fakeDetectorV1 struct{}
-type fakeDetectorV2 struct{}
-
-var _ detectors.Detector = (*fakeDetectorV1)(nil)
-var _ detectors.Versioner = (*fakeDetectorV1)(nil)
-var _ detectors.Detector = (*fakeDetectorV2)(nil)
-var _ detectors.Versioner = (*fakeDetectorV2)(nil)
-
-func (f fakeDetectorV1) FromData(_ aCtx.Context, _ bool, _ []byte) ([]detectors.Result, error) {
- return []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType(-1),
- Verified: true,
- Raw: []byte("fake secret v1"),
- },
- }, nil
-}
-
-func (f fakeDetectorV1) Keywords() []string { return []string{fakeDetectorKeyword} }
-func (f fakeDetectorV1) Type() detectorspb.DetectorType { return detectorspb.DetectorType(-1) }
-func (f fakeDetectorV1) Version() int { return 1 }
-
-func (f fakeDetectorV1) Description() string { return "fake detector v1" }
-
-func (f fakeDetectorV2) FromData(_ aCtx.Context, _ bool, _ []byte) ([]detectors.Result, error) {
- return []detectors.Result{
- {
- DetectorType: detectorspb.DetectorType(-1),
- Verified: true,
- Raw: []byte("fake secret v2"),
- },
- }, nil
-}
-
-func (f fakeDetectorV2) Keywords() []string { return []string{fakeDetectorKeyword} }
-func (f fakeDetectorV2) Type() detectorspb.DetectorType { return detectorspb.DetectorType(-1) }
-func (f fakeDetectorV2) Version() int { return 2 }
-
-func (f fakeDetectorV2) Description() string { return "fake detector v2" }
-
-func TestFragmentLineOffset(t *testing.T) {
- tests := []struct {
- name string
- chunk *sources.Chunk
- result *detectors.Result
- expectedLine int64
- ignore bool
- }{
- {
- name: "ignore found on same line",
- chunk: &sources.Chunk{
- Data: []byte("line1\nline2\nsecret here trufflehog:ignore\nline4"),
- },
- result: &detectors.Result{
- Raw: []byte("secret here"),
- },
- expectedLine: 2,
- ignore: true,
- },
- {
- name: "no ignore",
- chunk: &sources.Chunk{
- Data: []byte("line1\nline2\nsecret here\nline4"),
- },
- result: &detectors.Result{
- Raw: []byte("secret here"),
- },
- expectedLine: 2,
- ignore: false,
- },
- {
- name: "ignore on different line",
- chunk: &sources.Chunk{
- Data: []byte("line1\nline2\ntrufflehog:ignore\nline4\nsecret here\nline6"),
- },
- result: &detectors.Result{
- Raw: []byte("secret here"),
- },
- expectedLine: 4,
- ignore: false,
- },
- {
- name: "match on consecutive lines",
- chunk: &sources.Chunk{
- Data: []byte("line1\nline2\ntrufflehog:ignore\nline4\nsecret\nhere\nline6"),
- },
- result: &detectors.Result{
- Raw: []byte("secret\nhere"),
- },
- expectedLine: 4,
- ignore: false,
- },
- {
- name: "ignore on last consecutive lines",
- chunk: &sources.Chunk{
- Data: []byte("line1\nline2\nline3\nsecret\nhere // trufflehog:ignore\nline5"),
- },
- result: &detectors.Result{
- Raw: []byte("secret\nhere"),
- },
- expectedLine: 3,
- ignore: true,
- },
- {
- name: "ignore on last line",
- chunk: &sources.Chunk{
- Data: []byte("line1\nline2\nline3\nsecret here // trufflehog:ignore"),
- },
- result: &detectors.Result{
- Raw: []byte("secret here"),
- },
- expectedLine: 3,
- ignore: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- lineOffset, isIgnored := FragmentLineOffset(tt.chunk, tt.result)
- if lineOffset != tt.expectedLine {
- t.Errorf("Expected line offset to be %d, got %d", tt.expectedLine, lineOffset)
- }
- if isIgnored != tt.ignore {
- t.Errorf("Expected isIgnored to be %v, got %v", tt.ignore, isIgnored)
- }
- })
- }
-}
-
-func TestFragmentLineOffsetWithPrimarySecret(t *testing.T) {
- primarySecretResult1 := &detectors.Result{
- Raw: []byte("id heresecret here"), // RAW has two secrets merged
- }
-
- primarySecretResult1.SetPrimarySecretValue("secret here") // set `secret here` as primary secret value for line number calculation
-
- primarySecretResult2 := &detectors.Result{
- Raw: []byte("idsecret"), // RAW has two secrets merged
- }
-
- tests := []struct {
- name string
- chunk *sources.Chunk
- result *detectors.Result
- expectedLine int64
- ignore bool
- }{
- {
- name: "primary secret line number - correct line number",
- chunk: &sources.Chunk{
- Data: []byte("line1\nline2\nid here\nsecret here\nline5"),
- },
- result: primarySecretResult1,
- expectedLine: 3,
- ignore: false,
- },
- {
- name: "no primary secret set - wrong line number",
- chunk: &sources.Chunk{
- Data: []byte("line1\nline2\nid\nsecret\nline5"),
- },
- result: primarySecretResult2,
- expectedLine: 0,
- ignore: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- lineOffset, isIgnored := FragmentLineOffset(tt.chunk, tt.result)
- if lineOffset != tt.expectedLine {
- t.Errorf("Expected line offset to be %d, got %d", tt.expectedLine, lineOffset)
- }
- if isIgnored != tt.ignore {
- t.Errorf("Expected isIgnored to be %v, got %v", tt.ignore, isIgnored)
- }
- })
- }
-}
-
-func setupFragmentLineOffsetBench(totalLines, needleLine int) (*sources.Chunk, *detectors.Result) {
- data := make([]byte, 0, 4096)
- needle := []byte("needle")
- for i := 0; i < totalLines; i++ {
- if i != needleLine {
- data = append(data, []byte(fmt.Sprintf("line%d\n", i))...)
- continue
- }
- data = append(data, needle...)
- data = append(data, '\n')
- }
- chunk := &sources.Chunk{Data: data}
- result := &detectors.Result{Raw: needle}
- return chunk, result
-}
-
-func BenchmarkFragmentLineOffsetStart(b *testing.B) {
- chunk, result := setupFragmentLineOffsetBench(512, 2)
- for i := 0; i < b.N; i++ {
- _, _ = FragmentLineOffset(chunk, result)
- }
-}
-
-func BenchmarkFragmentLineOffsetMiddle(b *testing.B) {
- chunk, result := setupFragmentLineOffsetBench(512, 256)
- for i := 0; i < b.N; i++ {
- _, _ = FragmentLineOffset(chunk, result)
- }
-}
-
-func BenchmarkFragmentLineOffsetEnd(b *testing.B) {
- chunk, result := setupFragmentLineOffsetBench(512, 510)
- for i := 0; i < b.N; i++ {
- _, _ = FragmentLineOffset(chunk, result)
- }
-}
-
-// Test to make sure that DefaultDecoders always returns the UTF8 decoder first.
-// Technically a decoder test but we want this to run and fail in CI
-func TestDefaultDecoders(t *testing.T) {
- ds := decoders.DefaultDecoders()
- if _, ok := ds[0].(*decoders.UTF8); !ok {
- t.Errorf("DefaultDecoders() = %v, expected UTF8 decoder to be first", ds)
- }
-}
-
-func TestSupportsLineNumbers(t *testing.T) {
- tests := []struct {
- name string
- sourceType sourcespb.SourceType
- expectedValue bool
- }{
- {"Git source", sourcespb.SourceType_SOURCE_TYPE_GIT, true},
- {"Github source", sourcespb.SourceType_SOURCE_TYPE_GITHUB, true},
- {"Gitlab source", sourcespb.SourceType_SOURCE_TYPE_GITLAB, true},
- {"Bitbucket source", sourcespb.SourceType_SOURCE_TYPE_BITBUCKET, true},
- {"Gerrit source", sourcespb.SourceType_SOURCE_TYPE_GERRIT, true},
- {"Github unauthenticated org source", sourcespb.SourceType_SOURCE_TYPE_GITHUB_UNAUTHENTICATED_ORG, true},
- {"Public Git source", sourcespb.SourceType_SOURCE_TYPE_PUBLIC_GIT, true},
- {"Filesystem source", sourcespb.SourceType_SOURCE_TYPE_FILESYSTEM, true},
- {"Azure Repos source", sourcespb.SourceType_SOURCE_TYPE_AZURE_REPOS, true},
- {"Unsupported type", sourcespb.SourceType_SOURCE_TYPE_BUILDKITE, false},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := SupportsLineNumbers(tt.sourceType)
- assert.Equal(t, tt.expectedValue, result)
- })
- }
-}
-
-func BenchmarkSupportsLineNumbersLoop(b *testing.B) {
- sourceType := sourcespb.SourceType_SOURCE_TYPE_GITHUB
- for i := 0; i < b.N; i++ {
- _ = SupportsLineNumbers(sourceType)
- }
-}
-
-// TestEngine_DuplicateSecrets is a test that detects ALL duplicate secrets with the same decoder.
-func TestEngine_DuplicateSecrets(t *testing.T) {
- ctx := context.Background()
-
- absPath, err := filepath.Abs("./testdata/secrets.txt")
- assert.Nil(t, err)
-
- ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
- defer cancel()
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
-
- conf := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: defaults.DefaultDetectors(),
- Verify: false,
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- e, err := NewEngine(ctx, &conf)
- assert.NoError(t, err)
-
- e.Start(ctx)
-
- cfg := sources.FilesystemConfig{Paths: []string{absPath}}
- if _, err := e.ScanFileSystem(ctx, cfg); err != nil {
- return
- }
-
- // Wait for all the chunks to be processed.
- assert.Nil(t, e.Finish(ctx))
- want := uint64(2)
- assert.Equal(t, want, e.GetMetrics().UnverifiedSecretsFound)
-}
-
-// lineCaptureDispatcher is a test dispatcher that captures the line number
-// of detected secrets. It implements the Dispatcher interface and is used
-// to verify that the Engine correctly identifies and reports the line numbers
-// where secrets are found in the source code.
-type lineCaptureDispatcher struct{ line int64 }
-
-func (d *lineCaptureDispatcher) Dispatch(_ context.Context, result detectors.ResultWithMetadata) error {
- d.line = result.SourceMetadata.GetFilesystem().GetLine()
- return nil
-}
-
-func TestEngineLineVariations(t *testing.T) {
- tests := []struct {
- name string
- content string
- expectedLine int64
- }{
- {
- name: "secret on first line",
- content: `AKIA2OGYBAH6STMMNXNN
-aws_secret_access_key = 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`,
- expectedLine: 1,
- },
- {
- name: "secret after multiple newlines",
- content: `
-
-
-AKIA2OGYBAH6STMMNXNN
-aws_secret_access_key = 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`,
- expectedLine: 4,
- },
- {
- name: "secret with mixed whitespace before",
- content: `first line
-
-
-AKIA2OGYBAH6STMMNXNN
-aws_secret_access_key = 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`,
- expectedLine: 4,
- },
- {
- name: "secret with content after",
- content: `[default]
-region = us-east-1
-AKIA2OGYBAH6STMMNXNN
-aws_secret_access_key = 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
-more content
-even more`,
- expectedLine: 3,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
-
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- tmpFile, err := os.CreateTemp("", "test_aws_credentials")
- assert.NoError(t, err)
- defer os.Remove(tmpFile.Name())
-
- err = os.WriteFile(tmpFile.Name(), []byte(tt.content), os.ModeAppend)
- assert.NoError(t, err)
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
- lineCapturer := new(lineCaptureDispatcher)
-
- conf := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: defaults.DefaultDetectors(),
- Verify: false,
- SourceManager: sourceManager,
- Dispatcher: lineCapturer,
- }
-
- eng, err := NewEngine(ctx, &conf)
- assert.NoError(t, err)
-
- eng.Start(ctx)
-
- cfg := sources.FilesystemConfig{Paths: []string{tmpFile.Name()}}
- _, err = eng.ScanFileSystem(ctx, cfg)
- assert.NoError(t, err)
-
- assert.NoError(t, eng.Finish(ctx))
- want := uint64(1)
- assert.Equal(t, want, eng.GetMetrics().UnverifiedSecretsFound)
- assert.Equal(t, tt.expectedLine, lineCapturer.line)
- })
- }
-}
-
-// TestEngine_VersionedDetectorsVerifiedSecrets is a test that detects ALL verified secrets across
-// versioned detectors.
-func TestEngine_VersionedDetectorsVerifiedSecrets(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
- defer cancel()
-
- tmpFile, err := os.CreateTemp("", "testfile")
- assert.Nil(t, err)
- defer tmpFile.Close()
- defer os.Remove(tmpFile.Name())
-
- _, err = tmpFile.WriteString(fmt.Sprintf("test data using keyword %s", fakeDetectorKeyword))
- assert.NoError(t, err)
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
-
- conf := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: []detectors.Detector{new(fakeDetectorV1), new(fakeDetectorV2)},
- Verify: true,
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- e, err := NewEngine(ctx, &conf)
- assert.NoError(t, err)
-
- e.Start(ctx)
-
- cfg := sources.FilesystemConfig{Paths: []string{tmpFile.Name()}}
- if _, err := e.ScanFileSystem(ctx, cfg); err != nil {
- return
- }
-
- assert.NoError(t, e.Finish(ctx))
- want := uint64(2)
- assert.Equal(t, want, e.GetMetrics().VerifiedSecretsFound)
-}
-
-// TestEngine_CustomDetectorsDetectorsVerifiedSecrets is a test that covers an edge case where there are
-// multiple detectors with the same type, keywords and regex that match the same secret.
-// This ensures that those secrets get verified.
-func TestEngine_CustomDetectorsDetectorsVerifiedSecrets(t *testing.T) {
- tmpFile, err := os.CreateTemp("", "testfile")
- assert.Nil(t, err)
- defer tmpFile.Close()
- defer os.Remove(tmpFile.Name())
-
- _, err = tmpFile.WriteString("test stuff")
- assert.Nil(t, err)
-
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusOK)
- }))
- defer ts.Close()
-
- customDetector1, err := custom_detectors.NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{
- Name: "custom detector 1",
- Keywords: []string{"test"},
- Regex: map[string]string{"test": "\\w+"},
- Verify: []*custom_detectorspb.VerifierConfig{{Endpoint: ts.URL, Unsafe: true, SuccessRanges: []string{"200"}}},
- })
- assert.Nil(t, err)
-
- customDetector2, err := custom_detectors.NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{
- Name: "custom detector 2",
- Keywords: []string{"test"},
- Regex: map[string]string{"test": "\\w+"},
- Verify: []*custom_detectorspb.VerifierConfig{{Endpoint: ts.URL, Unsafe: true, SuccessRanges: []string{"200"}}},
- })
- assert.Nil(t, err)
-
- allDetectors := []detectors.Detector{customDetector1, customDetector2}
-
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
-
- conf := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: allDetectors,
- Verify: true,
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- e, err := NewEngine(ctx, &conf)
- assert.NoError(t, err)
-
- e.Start(ctx)
-
- cfg := sources.FilesystemConfig{Paths: []string{tmpFile.Name()}}
- if _, err := e.ScanFileSystem(ctx, cfg); err != nil {
- return
- }
-
- assert.Nil(t, e.Finish(ctx))
- // We should have 4 verified secrets, 2 for each custom detector.
- want := uint64(4)
- assert.Equal(t, want, e.GetMetrics().VerifiedSecretsFound)
-}
-
-func TestProcessResult_SourceSupportsLineNumbers_LinkUpdated(t *testing.T) {
- // Arrange: Create an engine
- e := Engine{results: make(chan detectors.ResultWithMetadata, 1)}
-
- // Arrange: Create a detectableChunk
- data := detectableChunk{
- chunk: sources.Chunk{
- Data: []byte("abcde\nswordfish"),
- SourceMetadata: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Github{
- Github: &source_metadatapb.Github{
- Line: 1,
- Link: "https://github.com/org/repo/blob/abcdef/file.txt#L1",
- },
- },
- },
- SourceType: sourcespb.SourceType_SOURCE_TYPE_GIT,
- },
- detector: &ahocorasick.DetectorMatch{Detector: fakeDetectorV1{}},
- }
-
- // Arrange: Create a Result
- result := detectors.Result{
- Raw: []byte("swordfish"),
- Verified: true,
- }
-
- // Act
- e.processResult(context.AddLogger(t.Context()), data, result, nil)
-
- // Assert that the link has been correctly updated
- require.Len(t, e.results, 1)
- r := <-e.results
- assert.Equal(t, "https://github.com/org/repo/blob/abcdef/file.txt#L2", r.SourceMetadata.GetGithub().GetLink())
-}
-
-func TestProcessResult_IgnoreLinePresent_NothingGenerated(t *testing.T) {
- // Arrange: Create an engine
- e := Engine{results: make(chan detectors.ResultWithMetadata, 1)}
-
- // Arrange: Create a detectableChunk
- data := detectableChunk{
- chunk: sources.Chunk{
- Data: []byte("swordfish trufflehog:ignore"),
- SourceMetadata: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Git{
- Git: &source_metadatapb.Git{
- Line: 1,
- },
- },
- },
- SourceType: sourcespb.SourceType_SOURCE_TYPE_GIT,
- },
- detector: &ahocorasick.DetectorMatch{Detector: fakeDetectorV1{}},
- }
-
- // Arrange: Create a Result
- result := detectors.Result{
- Raw: []byte("swordfish"),
- Verified: true,
- }
-
- // Act
- e.processResult(context.AddLogger(t.Context()), data, result, nil)
-
- // Assert that no results were generated
- assert.Empty(t, e.results)
-}
-
-func TestProcessResult_AllFieldsCopied(t *testing.T) {
- // Arrange: Create an engine
- e := Engine{results: make(chan detectors.ResultWithMetadata, 1)}
-
- // Arrange: Create a detectableChunk
- data := detectableChunk{
- chunk: sources.Chunk{
- SourceName: "test source",
- SourceID: 1,
- JobID: 2,
- SecretID: 3,
- SourceMetadata: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Docker{
- Docker: &source_metadatapb.Docker{
- File: "file",
- Image: "image",
- Layer: "layer",
- Tag: "tag",
- },
- },
- },
- SourceType: sourcespb.SourceType_SOURCE_TYPE_DOCKER,
- },
- decoder: detectorspb.DecoderType_PLAIN,
- detector: &ahocorasick.DetectorMatch{Detector: fakeDetectorV1{}},
- }
-
- // Arrange: Create a Result
- result := detectors.Result{
- DetectorType: TestDetectorType,
- ExtraData: map[string]string{"key": "value"},
- Raw: []byte("something"),
- RawV2: []byte("something:else"),
- Redacted: "someth***",
- Verified: true,
- }
-
- // Act
- e.processResult(context.AddLogger(t.Context()), data, result, nil)
-
- // Assert that the single generated result has the correct fields
- require.Len(t, e.results, 1)
- r := <-e.results
- if diff := cmp.Diff(data.chunk.SourceMetadata, r.SourceMetadata, protocmp.Transform()); diff != "" {
- t.Errorf("metadata mismatch (-want +got):\n%s", diff)
- }
- assert.Equal(t, map[string]string{"key": "value"}, r.ExtraData)
- assert.Equal(t, []byte("something"), r.Raw)
- assert.Equal(t, []byte("something:else"), r.RawV2)
- assert.Equal(t, "someth***", r.Redacted)
- assert.True(t, r.Verified)
- assert.Equal(t, detectorspb.DetectorType(TestDetectorType), r.DetectorType)
- assert.Equal(t, sources.SourceID(1), r.SourceID)
- assert.Equal(t, sources.JobID(2), r.JobID)
- assert.Equal(t, int64(3), r.SecretID)
- assert.Equal(t, "test source", r.SourceName)
- assert.Equal(t, sourcespb.SourceType_SOURCE_TYPE_DOCKER, r.SourceType)
- assert.Equal(t, detectorspb.DecoderType_PLAIN, r.DecoderType)
-}
-
-func TestProcessResult_FalsePositiveFlagSetCorrectly(t *testing.T) {
- testcases := []struct {
- name string
- verified bool
- isFalsePositive bool
- wantIsFalsePositive bool
- }{
- {
- name: "unverified/false positive",
- verified: false,
- isFalsePositive: true,
- wantIsFalsePositive: true,
- },
- {
- name: "unverified/not false positive",
- verified: false,
- isFalsePositive: false,
- wantIsFalsePositive: false,
- },
- {
- name: "verified/false positive",
- verified: true,
- isFalsePositive: true,
- wantIsFalsePositive: false, // The false positive check should not be run for verified secrets
- },
- {
- name: "verified/not false positive",
- verified: true,
- isFalsePositive: false,
- wantIsFalsePositive: false,
- },
- }
-
- for _, tt := range testcases {
- t.Run(tt.name, func(t *testing.T) {
- // Arrange: Create an Engine
- e := Engine{results: make(chan detectors.ResultWithMetadata, 1)}
-
- // Arrange: Create a detectableChunk
- // (It needs a DetectorMatch to avoid a panic.)
- data := detectableChunk{detector: &ahocorasick.DetectorMatch{Detector: fakeDetectorV1{}}}
-
- // Arrange: Create a Result
- res := detectors.Result{
- Raw: []byte("something not nil"), // The false positive check is not run when Raw is nil
- Verified: tt.verified,
- }
-
- // Arrange: Create the false positive check
- isFalsePositive := func(_ detectors.Result) (bool, string) { return tt.isFalsePositive, "" }
-
- // Act
- e.processResult(context.AddLogger(t.Context()), data, res, isFalsePositive)
-
- // Assert that the single generated result has the correct false positive flag
- require.Len(t, e.results, 1)
- assert.Equal(t, tt.wantIsFalsePositive, (<-e.results).IsWordlistFalsePositive)
- })
- }
-}
-
-func TestVerificationOverlapChunk(t *testing.T) {
- ctx := context.Background()
-
- absPath, err := filepath.Abs("./testdata/verificationoverlap_secrets.txt")
- assert.Nil(t, err)
-
- ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
- defer cancel()
-
- confPath, err := filepath.Abs("./testdata/verificationoverlap_detectors.yaml")
- assert.Nil(t, err)
- conf, err := config.Read(confPath)
- assert.Nil(t, err)
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
-
- c := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: conf.Detectors,
- IncludeDetectors: "904", // isolate this test to only the custom detectors provided
- Verify: false,
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- e, err := NewEngine(ctx, &c)
- assert.NoError(t, err)
-
- e.verificationOverlapTracker = new(verificationOverlapTracker)
-
- e.Start(ctx)
-
- cfg := sources.FilesystemConfig{Paths: []string{absPath}}
- if _, err := e.ScanFileSystem(ctx, cfg); err != nil {
- return
- }
-
- // Wait for all the chunks to be processed.
- assert.Nil(t, e.Finish(ctx))
- // We want TWO secrets that match both the custom regexes.
- want := uint64(2)
- assert.Equal(t, want, e.GetMetrics().UnverifiedSecretsFound)
-
- // We want 0 because these are custom detectors and verification should still occur.
- wantDupe := 0
- assert.Equal(t, wantDupe, e.verificationOverlapTracker.verificationOverlapDuplicateCount)
-}
-
-const (
- TestDetectorType = -1
- TestDetectorType2 = -2
-)
-
-var _ detectors.Detector = (*testDetectorV1)(nil)
-
-type testDetectorV1 struct{}
-
-func (testDetectorV1) FromData(_ aCtx.Context, _ bool, _ []byte) ([]detectors.Result, error) {
- result := detectors.Result{
- DetectorType: TestDetectorType,
- Raw: []byte("ssample-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r"),
- }
- return []detectors.Result{result}, nil
-}
-
-func (testDetectorV1) Keywords() []string { return []string{"sample"} }
-
-func (testDetectorV1) Type() detectorspb.DetectorType { return TestDetectorType }
-
-func (testDetectorV1) Description() string { return "" }
-
-var _ detectors.Detector = (*testDetectorV2)(nil)
-
-type testDetectorV2 struct{}
-
-func (testDetectorV2) FromData(_ aCtx.Context, _ bool, _ []byte) ([]detectors.Result, error) {
- result := detectors.Result{
- DetectorType: TestDetectorType,
- Raw: []byte("sample-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r"),
- }
- return []detectors.Result{result}, nil
-}
-
-func (testDetectorV2) Keywords() []string { return []string{"ample"} }
-
-func (testDetectorV2) Type() detectorspb.DetectorType { return TestDetectorType2 }
-
-func (testDetectorV2) Description() string { return "" }
-
-func TestVerificationOverlapChunkFalsePositive(t *testing.T) {
- ctx := context.Background()
-
- absPath, err := filepath.Abs("./testdata/verificationoverlap_secrets_fp.txt")
- assert.NoError(t, err)
-
- ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
- defer cancel()
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
-
- c := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: []detectors.Detector{testDetectorV1{}, testDetectorV2{}},
- Verify: false,
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- e, err := NewEngine(ctx, &c)
- assert.NoError(t, err)
-
- e.verificationOverlapTracker = new(verificationOverlapTracker)
-
- e.Start(ctx)
-
- cfg := sources.FilesystemConfig{Paths: []string{absPath}}
- _, err = e.ScanFileSystem(ctx, cfg)
- assert.NoError(t, err)
-
- // Wait for all the chunks to be processed.
- assert.NoError(t, e.Finish(ctx))
- // We want 0 because the secret is a false positive.
- want := uint64(0)
- assert.Equal(t, want, e.GetMetrics().UnverifiedSecretsFound)
-}
-
-func TestRetainFalsePositives(t *testing.T) {
- ctx := context.Background()
-
- absPath, err := filepath.Abs("./testdata/verificationoverlap_secrets_fp.txt")
- assert.NoError(t, err)
-
- ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
- defer cancel()
-
- confPath, err := filepath.Abs("./testdata/verificationoverlap_detectors_fp.yaml")
- assert.NoError(t, err)
- conf, err := config.Read(confPath)
- assert.NoError(t, err)
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
-
- c := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: conf.Detectors,
- Verify: false,
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- Results: map[string]struct{}{"filtered_unverified": {}},
- }
-
- e, err := NewEngine(ctx, &c)
- assert.NoError(t, err)
-
- e.Start(ctx)
-
- cfg := sources.FilesystemConfig{Paths: []string{absPath}}
- _, err = e.ScanFileSystem(ctx, cfg)
- assert.NoError(t, err)
-
- // Wait for all the chunks to be processed.
- assert.NoError(t, e.Finish(ctx))
- // We want 1 because the secret is a false positive and we are retaining it.
- want := uint64(1)
- assert.Equal(t, want, e.GetMetrics().UnverifiedSecretsFound)
-}
-
-func TestFragmentFirstLineAndLink(t *testing.T) {
- tests := []struct {
- name string
- chunk *sources.Chunk
- expectedLine int64
- expectedLink string
- }{
- {
- name: "Test Git Metadata",
- chunk: &sources.Chunk{
- SourceMetadata: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Git{
- Git: &source_metadatapb.Git{
- Line: 10,
- },
- },
- },
- },
- expectedLine: 10,
- expectedLink: "", // Git doesn't support links
- },
- {
- name: "Test Github Metadata",
- chunk: &sources.Chunk{
- SourceMetadata: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Github{
- Github: &source_metadatapb.Github{
- Line: 5,
- Link: "https://example.github.com",
- },
- },
- },
- },
- expectedLine: 5,
- expectedLink: "https://example.github.com",
- },
- {
- name: "Test Azure Repos Metadata",
- chunk: &sources.Chunk{
- SourceMetadata: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_AzureRepos{
- AzureRepos: &source_metadatapb.AzureRepos{
- Line: 5,
- Link: "https://example.azure.com",
- },
- },
- },
- },
- expectedLine: 5,
- expectedLink: "https://example.azure.com",
- },
- {
- name: "Line number not set",
- chunk: &sources.Chunk{
- SourceMetadata: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Github{
- Github: &source_metadatapb.Github{
- Link: "https://example.github.com",
- },
- },
- },
- },
- expectedLine: 1,
- expectedLink: "https://example.github.com",
- },
- {
- name: "Unsupported Type",
- chunk: &sources.Chunk{},
- expectedLine: 0,
- expectedLink: "",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- line, linePtr, link := FragmentFirstLineAndLink(tt.chunk)
- assert.Equal(t, tt.expectedLink, link, "Mismatch in link")
- assert.Equal(t, tt.expectedLine, line, "Mismatch in line")
-
- if linePtr != nil {
- assert.Equal(t, tt.expectedLine, *linePtr, "Mismatch in linePtr value")
- }
- })
- }
-}
-
-func TestSetLink(t *testing.T) {
- tests := []struct {
- name string
- input *source_metadatapb.MetaData
- link string
- line int64
- wantLink string
- wantErr bool
- }{
- {
- name: "Github link set",
- input: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Github{
- Github: &source_metadatapb.Github{},
- },
- },
- link: "https://github.com/example",
- line: 42,
- wantLink: "https://github.com/example#L42",
- },
- {
- name: "Gitlab link set",
- input: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Gitlab{
- Gitlab: &source_metadatapb.Gitlab{},
- },
- },
- link: "https://gitlab.com/example",
- line: 10,
- wantLink: "https://gitlab.com/example#L10",
- },
- {
- name: "Bitbucket link set",
- input: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Bitbucket{
- Bitbucket: &source_metadatapb.Bitbucket{},
- },
- },
- link: "https://bitbucket.com/example",
- line: 8,
- wantLink: "https://bitbucket.com/example#L8",
- },
- {
- name: "Filesystem link set",
- input: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Filesystem{
- Filesystem: &source_metadatapb.Filesystem{},
- },
- },
- link: "file:///path/to/example",
- line: 3,
- wantLink: "file:///path/to/example#L3",
- },
- {
- name: "Azure Repos link set",
- input: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_AzureRepos{
- AzureRepos: &source_metadatapb.AzureRepos{},
- },
- },
- link: "https://dev.azure.com/example",
- line: 3,
- wantLink: "https://dev.azure.com/example?line=3&lineEnd=4&lineStartColumn=1",
- },
- {
- name: "Unsupported metadata type",
- input: &source_metadatapb.MetaData{
- Data: &source_metadatapb.MetaData_Git{
- Git: &source_metadatapb.Git{},
- },
- },
- link: "https://git.example.com/link",
- line: 5,
- wantErr: true,
- },
- {
- name: "Metadata nil",
- input: nil,
- link: "https://some.link",
- line: 1,
- wantErr: true,
- },
- }
-
- ctx := context.Background()
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- err := UpdateLink(ctx, tt.input, tt.link, tt.line)
- if err != nil && !tt.wantErr {
- t.Errorf("Unexpected error: %v", err)
- }
-
- if tt.wantErr {
- return
- }
-
- switch data := tt.input.GetData().(type) {
- case *source_metadatapb.MetaData_Github:
- assert.Equal(t, tt.wantLink, data.Github.Link, "Github link mismatch")
- case *source_metadatapb.MetaData_Gitlab:
- assert.Equal(t, tt.wantLink, data.Gitlab.Link, "Gitlab link mismatch")
- case *source_metadatapb.MetaData_Bitbucket:
- assert.Equal(t, tt.wantLink, data.Bitbucket.Link, "Bitbucket link mismatch")
- case *source_metadatapb.MetaData_Filesystem:
- assert.Equal(t, tt.wantLink, data.Filesystem.Link, "Filesystem link mismatch")
- case *source_metadatapb.MetaData_AzureRepos:
- assert.Equal(t, tt.wantLink, data.AzureRepos.Link, "Azure Repos link mismatch")
- }
- })
- }
-}
-
-func TestLikelyDuplicate(t *testing.T) {
- // Initialize detectors
- // (not actually calling detector FromData or anything, just using detector struct for key creation)
- detectorA := ahocorasick.DetectorMatch{
- Key: ahocorasick.CreateDetectorKey(defaults.DefaultDetectors()[0]),
- Detector: defaults.DefaultDetectors()[0],
- }
- detectorB := ahocorasick.DetectorMatch{
- Key: ahocorasick.CreateDetectorKey(defaults.DefaultDetectors()[1]),
- Detector: defaults.DefaultDetectors()[1],
- }
-
- // Define test cases
- tests := []struct {
- name string
- val chunkSecretKey
- dupes map[chunkSecretKey]struct{}
- expected bool
- }{
- {
- name: "exact duplicate different detector",
- val: chunkSecretKey{"PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r", detectorA.Key},
- dupes: map[chunkSecretKey]struct{}{
- {"PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r", detectorB.Key}: {},
- },
- expected: true,
- },
- {
- name: "non-duplicate length outside range",
- val: chunkSecretKey{"short", detectorA.Key},
- dupes: map[chunkSecretKey]struct{}{
- {"muchlongerthanthevalstring", detectorB.Key}: {},
- },
- expected: false,
- },
- {
- name: "similar within threshold",
- val: chunkSecretKey{"PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r", detectorA.Key},
- dupes: map[chunkSecretKey]struct{}{
- {"qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r", detectorB.Key}: {},
- },
- expected: true,
- },
- {
- name: "similar outside threshold",
- val: chunkSecretKey{"anotherkey", detectorA.Key},
- dupes: map[chunkSecretKey]struct{}{
- {"completelydifferent", detectorB.Key}: {},
- },
- expected: false,
- },
- {
- name: "empty strings",
- val: chunkSecretKey{"", detectorA.Key},
- dupes: map[chunkSecretKey]struct{}{{"", detectorB.Key}: {}},
- expected: true,
- },
- {
- name: "similar within threshold same detector",
- val: chunkSecretKey{"PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r", detectorA.Key},
- dupes: map[chunkSecretKey]struct{}{
- {"qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r", detectorA.Key}: {},
- },
- expected: false,
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- result := likelyDuplicate(ctx, tc.val, tc.dupes)
- if result != tc.expected {
- t.Errorf("expected %v, got %v", tc.expected, result)
- }
- })
- }
-}
-
-type customCleaner struct {
- ignoreConfig bool
-}
-
-var _ detectors.CustomResultsCleaner = (*customCleaner)(nil)
-var _ detectors.Detector = (*customCleaner)(nil)
-
-func (c customCleaner) FromData(aCtx.Context, bool, []byte) ([]detectors.Result, error) {
- return []detectors.Result{}, nil
-}
-
-func (c customCleaner) Keywords() []string { return []string{} }
-func (c customCleaner) Type() detectorspb.DetectorType { return detectorspb.DetectorType(-1) }
-
-func (customCleaner) Description() string { return "" }
-
-func (c customCleaner) CleanResults([]detectors.Result) []detectors.Result {
- return []detectors.Result{}
-}
-func (c customCleaner) ShouldCleanResultsIrrespectiveOfConfiguration() bool { return c.ignoreConfig }
-
-func TestFilterResults_CustomCleaner(t *testing.T) {
- testCases := []struct {
- name string
- cleaningConfigured bool
- ignoreConfig bool
- resultsToClean []detectors.Result
- wantResults []detectors.Result
- }{
- {
- name: "respect config to clean",
- cleaningConfigured: true,
- ignoreConfig: false,
- resultsToClean: []detectors.Result{{}},
- wantResults: []detectors.Result{},
- },
- {
- name: "respect config to not clean",
- cleaningConfigured: false,
- ignoreConfig: false,
- resultsToClean: []detectors.Result{{}},
- wantResults: []detectors.Result{{}},
- },
- {
- name: "clean irrespective of config",
- cleaningConfigured: false,
- ignoreConfig: true,
- resultsToClean: []detectors.Result{{}},
- wantResults: []detectors.Result{},
- },
- }
-
- for _, tt := range testCases {
- t.Run(tt.name, func(t *testing.T) {
- match := ahocorasick.DetectorMatch{
- Detector: customCleaner{
- ignoreConfig: tt.ignoreConfig,
- },
- }
- engine := Engine{
- filterUnverified: tt.cleaningConfigured,
- retainFalsePositives: true,
- }
-
- cleaned := engine.filterResults(context.Background(), &match, tt.resultsToClean)
-
- assert.ElementsMatch(t, tt.wantResults, cleaned)
- })
- }
-}
-
-func BenchmarkPopulateMatchingDetectors(b *testing.B) {
- allDetectors := defaults.DefaultDetectors()
- ac := ahocorasick.NewAhoCorasickCore(allDetectors)
-
- // Generate sample data with keywords from detectors.
- dataSize := 1 << 20 // 1 MB
- sampleData := generateRandomDataWithKeywords(dataSize, allDetectors)
-
- smallChunk := 1 << 10 // 1 KB
- mediumChunk := 1 << 12 // 4 KB
- current := sources.TotalChunkSize
- largeChunk := 1 << 14 // 16 KB
- xlChunk := 1 << 15 // 32 KB
- xxlChunk := 1 << 16 // 64 KB
- xxxlChunk := 1 << 18 // 256 KB
- chunkSizes := []int{smallChunk, mediumChunk, current, largeChunk, xlChunk, xxlChunk, xxxlChunk}
-
- for _, chunkSize := range chunkSizes {
- b.Run(fmt.Sprintf("ChunkSize_%d", chunkSize), func(b *testing.B) {
- b.ReportAllocs()
- b.SetBytes(int64(chunkSize))
-
- // Create a single chunk of the desired size.
- chunk := sampleData[:chunkSize]
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- ac.FindDetectorMatches([]byte(chunk)) // Match against the single chunk
- }
- })
- }
-}
-
-func generateRandomDataWithKeywords(size int, detectors []detectors.Detector) string {
- const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
- data := make([]byte, size)
-
- r := rand.New(rand.NewSource(42)) // Seed for reproducibility
-
- for i := range data {
- data[i] = charset[r.Intn(len(charset))]
- }
-
- totalKeywords := 0
- for _, d := range detectors {
- totalKeywords += len(d.Keywords())
- }
-
- // Target keyword density (keywords per character)
- // This ensures that the generated data has a reasonable number of keywords and is consistent
- // across different data sizes.
- keywordDensity := 0.01
-
- targetKeywords := int(float64(size) * keywordDensity)
-
- for i := 0; i < targetKeywords; i++ {
- detectorIndex := r.Intn(len(detectors))
- keywordIndex := r.Intn(len(detectors[detectorIndex].Keywords()))
- keyword := detectors[detectorIndex].Keywords()[keywordIndex]
-
- insertPosition := r.Intn(size - len(keyword))
- copy(data[insertPosition:], keyword)
- }
-
- return string(data)
-}
-
-func TestEngine_ShouldVerifyChunk(t *testing.T) {
- tests := []struct {
- name string
- detector detectors.Detector
- overrideKey config.DetectorID
- want func(sourceVerify, detectorVerify bool) bool
- }{
- {
- name: "detector override by exact version",
- detector: &gitlab.Scanner{},
- overrideKey: config.DetectorID{ID: detectorspb.DetectorType_Gitlab, Version: 2},
- want: func(sourceVerify, detectorVerify bool) bool { return detectorVerify },
- },
- {
- name: "detector override by versionless config",
- detector: &gitlab.Scanner{},
- overrideKey: config.DetectorID{ID: detectorspb.DetectorType_Gitlab, Version: 0},
- want: func(sourceVerify, detectorVerify bool) bool { return detectorVerify },
- },
- {
- name: "no detector override because of detector type mismatch",
- detector: &gitlab.Scanner{},
- overrideKey: config.DetectorID{ID: detectorspb.DetectorType_NpmToken, Version: 2},
- want: func(sourceVerify, detectorVerify bool) bool { return sourceVerify },
- },
- {
- name: "no detector override because of detector version mismatch",
- detector: &gitlab.Scanner{},
- overrideKey: config.DetectorID{ID: detectorspb.DetectorType_Gitlab, Version: 1},
- want: func(sourceVerify, detectorVerify bool) bool { return sourceVerify },
- },
- }
-
- booleanChoices := [2]bool{true, false}
-
- engine := &Engine{verify: true}
-
- for _, tt := range tests {
- for _, sourceVerify := range booleanChoices {
- for _, detectorVerify := range booleanChoices {
-
- t.Run(fmt.Sprintf("%s (source verify = %v, detector verify = %v)", tt.name, sourceVerify, detectorVerify), func(t *testing.T) {
- overrides := map[config.DetectorID]bool{
- tt.overrideKey: detectorVerify,
- }
-
- want := tt.want(sourceVerify, detectorVerify)
-
- got := engine.shouldVerifyChunk(sourceVerify, tt.detector, overrides)
-
- assert.Equal(t, want, got)
- })
- }
- }
- }
-}
-
-func TestEngineInitializesCloudProviderDetectors(t *testing.T) {
- ctx := context.Background()
- conf := Config{
- Concurrency: 1,
- Detectors: defaults.DefaultDetectors(),
- Verify: false,
- SourceManager: sources.NewManager(),
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- e, err := NewEngine(ctx, &conf)
- assert.NoError(t, err)
-
- var count int
- for _, det := range e.detectors {
- if endpoints, ok := det.(interface{ Endpoints(...string) []string }); ok {
- id := config.GetDetectorID(det)
- if len(endpoints.Endpoints()) == 0 && det.Type() != detectorspb.DetectorType_ArtifactoryAccessToken && det.Type() != detectorspb.DetectorType_TableauPersonalAccessToken { // artifactory and tableau does not have any cloud endpoint
- t.Fatalf("detector %q Endpoints() is empty", id.String())
- }
- count++
- }
- }
-
- if count == 0 {
- t.Fatal("no detectors found implementing Endpoints(), did EndpointSetter change?")
- }
-}
-
-func TestEngineignoreLine(t *testing.T) {
- tests := []struct {
- name string
- content string
- expectedFindings int
- }{
- {
- name: "ignore at end of line",
- content: `
-# tests/example_false_positive.py
-
-def test_something():
- connection_string = "who-cares"
-
- # Ignoring this does not work
- assert connection_string == "postgres://master_user:master_password@hostname:1234/main" # trufflehog:ignore`,
- expectedFindings: 0,
- },
- {
- name: "ignore not on secret line",
- content: `
-# tests/example_false_positive.py
-
-def test_something():
- connection_string = "who-cares"
-
- # Ignoring this does not work
- assert some_other_stuff == "blah" # trufflehog:ignore
- assert connection_string == "postgres://master_user:master_password@hostname:1234/main"`,
- expectedFindings: 1,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
-
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- tmpFile, err := os.CreateTemp("", "test_creds")
- assert.NoError(t, err)
- defer os.Remove(tmpFile.Name())
-
- err = os.WriteFile(tmpFile.Name(), []byte(tt.content), os.ModeAppend)
- assert.NoError(t, err)
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
-
- conf := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: defaults.DefaultDetectors(),
- Verify: false,
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- eng, err := NewEngine(ctx, &conf)
- assert.NoError(t, err)
-
- eng.Start(ctx)
-
- cfg := sources.FilesystemConfig{Paths: []string{tmpFile.Name()}}
- _, err = eng.ScanFileSystem(ctx, cfg)
- assert.NoError(t, err)
-
- assert.NoError(t, eng.Finish(ctx))
- assert.Equal(t, tt.expectedFindings, int(eng.GetMetrics().UnverifiedSecretsFound))
- })
- }
-}
-
-type passthroughDetector struct {
- detectorType detectorspb.DetectorType
- keywords []string
- secret string
-}
-
-func (p passthroughDetector) FromData(_ aCtx.Context, verify bool, data []byte) ([]detectors.Result, error) {
- raw := data
- if p.secret != "" {
- raw = []byte(p.secret)
- }
- return []detectors.Result{
- {
- Raw: raw,
- Verified: verify,
- },
- }, nil
-}
-
-func (p passthroughDetector) Keywords() []string { return p.keywords }
-func (p passthroughDetector) Type() detectorspb.DetectorType { return p.detectorType }
-func (p passthroughDetector) Description() string { return "fake detector for testing" }
-
-type passthroughDecoder struct{}
-
-func (p passthroughDecoder) FromChunk(chunk *sources.Chunk) *decoders.DecodableChunk {
- return &decoders.DecodableChunk{
- Chunk: chunk,
- DecoderType: detectorspb.DecoderType(-1),
- }
-}
-
-func (p passthroughDecoder) Type() detectorspb.DecoderType { return detectorspb.DecoderType(-1) }
-
-func TestEngine_DetectChunk_UsesVerifyFlag(t *testing.T) {
- ctx := context.Background()
-
- // Arrange: Create a minimal engine.
- e := &Engine{
- results: make(chan detectors.ResultWithMetadata, 1),
- verificationCache: verificationcache.New(nil, &verificationcache.InMemoryMetrics{}),
- }
-
- // Arrange: Create a detector match. We can't create one directly, so we have to use a minimal A-H core.
- ahcore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{passthroughDetector{keywords: []string{"keyword"}}})
- detectorMatches := ahcore.FindDetectorMatches([]byte("keyword"))
- require.Len(t, detectorMatches, 1)
-
- // Arrange: Create a chunk to detect.
- chunk := detectableChunk{
- chunk: sources.Chunk{
- Verify: true,
- },
- detector: detectorMatches[0],
- wgDoneFn: func() {},
- }
-
- // Act
- e.detectChunk(ctx, chunk)
- close(e.results)
-
- // Assert: Confirm that a result was generated and that it has the expected verify flag.
- select {
- case result := <-e.results:
- assert.True(t, result.Result.Verified)
- default:
- t.Errorf("expected a result but did not get one")
- }
-}
-
-func TestEngine_ScannerWorker_DetectableChunkHasCorrectVerifyFlag(t *testing.T) {
- ctx := context.Background()
-
- // Arrange: Create a minimal engine.
- detector := &passthroughDetector{keywords: []string{"keyword"}}
- e := &Engine{
- AhoCorasickCore: ahocorasick.NewAhoCorasickCore([]detectors.Detector{detector}),
- decoders: []decoders.Decoder{passthroughDecoder{}},
- detectableChunksChan: make(chan detectableChunk, 1),
- sourceManager: sources.NewManager(),
- verify: true,
- }
-
- // Arrange: Create a chunk to scan.
- chunk := sources.Chunk{
- Data: []byte("keyword"),
- Verify: true,
- }
-
- // Arrange: Enqueue a chunk to be scanned.
- e.sourceManager.ScanChunk(&chunk)
-
- // Act
- go e.scannerWorker(ctx)
-
- // Assert: Confirm that a chunk was generated and that it has the expected verify flag.
- select {
- case chunk := <-e.detectableChunksChan:
- assert.True(t, chunk.chunk.Verify)
- case <-time.After(1 * time.Second):
- t.Errorf("expected a detectableChunk but did not get one")
- }
-}
-
-func TestEngine_VerificationOverlapWorker_DetectableChunkHasCorrectVerifyFlag(t *testing.T) {
- ctx := context.Background()
-
- t.Run("overlap", func(t *testing.T) {
- // Arrange: Create a minimal engine
- e := &Engine{
- detectableChunksChan: make(chan detectableChunk, 2),
- results: make(chan detectors.ResultWithMetadata, 2),
- retainFalsePositives: true,
- verificationOverlapChunksChan: make(chan verificationOverlapChunk, 2),
- verify: true,
- }
-
- // Arrange: Set up a fake detectableChunk processor so that any chunks (incorrectly) sent to
- // e.detectableChunksChan don't block the test.
- processedDetectableChunks := make(chan detectableChunk, 2)
- go func() {
- for chunk := range e.detectableChunksChan {
- chunk.wgDoneFn()
- processedDetectableChunks <- chunk
- }
- }()
-
- // Arrange: Create a chunk to "scan."
- chunk := sources.Chunk{
- Data: []byte("keyword ;oahpow8heg;blaisd"),
- Verify: true,
- }
-
- // Arrange: Create overlapping detector matches. We can't create them directly, so we have to use a minimal A-H
- // core.
- ahcore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{
- passthroughDetector{detectorType: detectorspb.DetectorType(-1), keywords: []string{"keyw"}},
- passthroughDetector{detectorType: detectorspb.DetectorType(-2), keywords: []string{"keyword"}},
- })
- detectorMatches := ahcore.FindDetectorMatches(chunk.Data)
- require.Len(t, detectorMatches, 2)
-
- // Arrange: Enqueue a verification overlap chunk
- e.verificationOverlapChunksChan <- verificationOverlapChunk{
- chunk: chunk,
- detectors: detectorMatches,
- verificationOverlapWgDoneFn: func() { close(e.verificationOverlapChunksChan) },
- }
-
- // Act
- e.verificationOverlapWorker(ctx)
- close(e.results)
- close(e.detectableChunksChan)
- close(processedDetectableChunks)
-
- // Assert: Confirm that every generated result is unverified (because overlap detection precluded it).
- for result := range e.results {
- assert.False(t, result.Result.Verified)
- }
-
- // Assert: Confirm that every generated detectable chunk carries the original Verify flag.
- // CMR: There should be not be any of these chunks. However, due to what I believe is an unrelated bug, there
- // are. This test ensures that even in that erroneous case, their Verify flag is correct.
- for detectableChunk := range processedDetectableChunks {
- assert.True(t, detectableChunk.chunk.Verify)
- }
- })
- t.Run("no overlap", func(t *testing.T) {
- // Arrange: Create a minimal engine
- e := &Engine{
- detectableChunksChan: make(chan detectableChunk, 2),
- retainFalsePositives: true,
- verificationOverlapChunksChan: make(chan verificationOverlapChunk, 2),
- verify: true,
- }
-
- // Arrange: Set up a fake detectableChunk processor so that any chunks sent to e.detectableChunksChan don't
- // block the test.
- processedDetectableChunks := make(chan detectableChunk, 2)
- go func() {
- for chunk := range e.detectableChunksChan {
- chunk.wgDoneFn()
- processedDetectableChunks <- chunk
- }
- }()
-
- // Arrange: Create a chunk to "scan."
- chunk := sources.Chunk{
- Data: []byte("keyword ;oahpow8heg;blaisd"),
- Verify: true,
- }
-
- // Arrange: Create non-overlapping detector matches. We can't create them directly, so we have to use a minimal
- // A-H core.
- ahcore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{
- passthroughDetector{detectorType: detectorspb.DetectorType(-1), keywords: []string{"keyw"}, secret: "oahpow"},
- passthroughDetector{detectorType: detectorspb.DetectorType(-2), keywords: []string{"keyword"}, secret: "blaisd"},
- })
- detectorMatches := ahcore.FindDetectorMatches(chunk.Data)
- require.Len(t, detectorMatches, 2)
-
- // Arrange: Enqueue a verification overlap chunk
- e.verificationOverlapChunksChan <- verificationOverlapChunk{
- chunk: chunk,
- detectors: detectorMatches,
- verificationOverlapWgDoneFn: func() { close(e.verificationOverlapChunksChan) },
- }
-
- // Act
- e.verificationOverlapWorker(ctx)
- close(e.detectableChunksChan)
- close(processedDetectableChunks)
-
- // Assert: Confirm that every generated detectable chunk carries the original Verify flag.
- for detectableChunk := range processedDetectableChunks {
- assert.True(t, detectableChunk.chunk.Verify)
- }
- })
-}
diff --git a/pkg/engine/filesystem.go b/pkg/engine/filesystem.go
deleted file mode 100644
index 90cc1521f15f..000000000000
--- a/pkg/engine/filesystem.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package engine
-
-import (
- "runtime"
-
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/filesystem"
-)
-
-// ScanFileSystem scans a given file system.
-func (e *Engine) ScanFileSystem(ctx context.Context, c sources.FilesystemConfig) (sources.JobProgressRef, error) {
- connection := &sourcespb.Filesystem{
- Paths: c.Paths,
- IncludePathsFile: c.IncludePathsFile,
- ExcludePathsFile: c.ExcludePathsFile,
- }
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal filesystem connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - filesystem"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, filesystem.SourceType)
-
- fileSystemSource := &filesystem.Source{}
- if err := fileSystemSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
- return sources.JobProgressRef{}, err
- }
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, fileSystemSource)
-}
diff --git a/pkg/engine/filesystem_integration_test.go b/pkg/engine/filesystem_integration_test.go
deleted file mode 100644
index f1dd2625d42a..000000000000
--- a/pkg/engine/filesystem_integration_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-//go:build integration
-// +build integration
-
-package engine
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-// createFilesystemTree is a helper function to create a temporary directory
-// that contains all of the provided files and contents on the operating
-// system's filesystem. Sub-directories will be created as needed. On success,
-// the root directory is returned and the caller is responsible for removing it
-// from the filesystem.
-func createFilesystemTree(files map[string]string) (string, error) {
- parentDir, err := os.MkdirTemp("", "trufflehog-integration-test")
- if err != nil {
- return "", err
- }
-
- for path, contents := range files {
- fullPath := filepath.Join(parentDir, path)
- if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
- _ = os.RemoveAll(parentDir)
- return "", err
- }
- if err := os.WriteFile(fullPath, []byte(contents), 0644); err != nil {
- _ = os.RemoveAll(parentDir)
- return "", err
- }
- }
- return parentDir, nil
-}
-
-func TestFilesystem(t *testing.T) {
- // Setup test directory.
- rootDir, err := createFilesystemTree(map[string]string{
- "/foo": "bar",
- "/bar": "baz",
- "/dir/a": "a",
- "/dir/b": "b",
- "/dir/c": "c",
- "/.ignore/file": "this should be ignored",
- "/.ignore/sub/dir": "this should also be ignored",
- })
- assert.NoError(t, err)
- defer os.RemoveAll(rootDir)
-
- configDir, err := createFilesystemTree(map[string]string{
- "/exclude": ".ignore",
- // TODO: Test include configuration.
- })
- assert.NoError(t, err)
- defer os.RemoveAll(configDir)
-
- // Run the scan.
- ctx := context.Background()
- e, err := NewEngine(ctx, &Config{
- Detectors: DefaultDetectors(),
- SourceManager: sources.NewManager(),
- Verify: false,
- })
- assert.NoError(t, err)
- e.Start(ctx)
- _, err = e.ScanFileSystem(ctx, sources.FilesystemConfig{
- Paths: []string{rootDir},
- ExcludePathsFile: filepath.Join(configDir, "exclude"),
- })
- assert.NoError(t, err)
-
- err = e.Finish(ctx)
- assert.NoError(t, err)
-
- // Check the output provided by metrics.
- metrics := e.GetMetrics()
- assert.Equal(t, uint64(5), metrics.ChunksScanned)
- assert.Equal(t, uint64(9), metrics.BytesScanned)
-}
diff --git a/pkg/engine/gcs.go b/pkg/engine/gcs.go
deleted file mode 100644
index 7ad54f273081..000000000000
--- a/pkg/engine/gcs.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package engine
-
-import (
- "fmt"
-
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/gcs"
-)
-
-// ScanGCS with the provided options.
-func (e *Engine) ScanGCS(ctx context.Context, c sources.GCSConfig) (sources.JobProgressRef, error) {
- // Project ID is required if using any authenticated access.
- if c.ProjectID == "" && !c.WithoutAuth {
- return sources.JobProgressRef{}, fmt.Errorf("project ID is required")
- }
-
- // If using unauthenticated access, the project ID is not used.
- if c.ProjectID != "" && c.WithoutAuth {
- c.ProjectID = ""
- ctx.Logger().Info("project ID is not used when using unauthenticated access, ignoring provided project ID")
- }
-
- connection := &sourcespb.GCS{
- ProjectId: c.ProjectID,
- IncludeBuckets: c.IncludeBuckets,
- ExcludeBuckets: c.ExcludeBuckets,
- IncludeObjects: c.IncludeObjects,
- ExcludeObjects: c.ExcludeObjects,
- }
-
- // Make sure only one auth method is selected.
- if !isAuthValid(ctx, c, connection) {
- return sources.JobProgressRef{}, fmt.Errorf("multiple auth methods selected, please select only one")
- }
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
- if err != nil {
- return sources.JobProgressRef{}, fmt.Errorf("failed to marshal GCS connection: %w", err)
- }
-
- sourceName := "trufflehog - gcs"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, gcs.SourceType)
-
- gcsSource := &gcs.Source{}
- if err := gcsSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, int(c.Concurrency)); err != nil {
- return sources.JobProgressRef{}, err
- }
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, gcsSource)
-}
-
-func isAuthValid(ctx context.Context, c sources.GCSConfig, connection *sourcespb.GCS) bool {
- var isAuthSelected bool
-
- if c.WithoutAuth {
- isAuthSelected = true
- connection.Credential = &sourcespb.GCS_Unauthenticated{}
- }
- if c.CloudCred {
- if isAuthSelected {
- return false
- }
- isAuthSelected = true
- connection.Credential = &sourcespb.GCS_Adc{}
- }
- if c.ServiceAccount != "" {
- if isAuthSelected {
- return false
- }
- isAuthSelected = true
- connection.Credential = &sourcespb.GCS_ServiceAccountFile{
- ServiceAccountFile: c.ServiceAccount,
- }
- }
- if c.ApiKey != "" {
- if isAuthSelected {
- return false
- }
- isAuthSelected = true
- connection.Credential = &sourcespb.GCS_ApiKey{
- ApiKey: c.ApiKey,
- }
- }
- if !isAuthSelected {
- ctx.Logger().Info("no auth method selected, using unauthenticated")
- connection.Credential = &sourcespb.GCS_Unauthenticated{}
- }
-
- return true
-}
diff --git a/pkg/engine/gcs_test.go b/pkg/engine/gcs_test.go
deleted file mode 100644
index c903e00eeca7..000000000000
--- a/pkg/engine/gcs_test.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package engine
-
-import (
- "strings"
- "testing"
-
- "github.com/stretchr/testify/assert"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-func TestScanGCS(t *testing.T) {
- tests := []struct {
- name string
- gcsConfig sources.GCSConfig
- wantErr bool
- }{
- {
- name: "scanned GCS",
- gcsConfig: sources.GCSConfig{
- ApiKey: "abc123",
- ProjectID: "test-project",
- CloudCred: false,
- WithoutAuth: false,
- ServiceAccount: "",
- },
- },
- {
- name: "missing project ID, with auth",
- gcsConfig: sources.GCSConfig{ApiKey: "abc123"},
- wantErr: true,
- },
- {
- name: "missing project ID, without auth, public scan",
- gcsConfig: sources.GCSConfig{WithoutAuth: true},
- },
- {
- name: "multiple selected auth methods",
- gcsConfig: sources.GCSConfig{
- ApiKey: "abc123",
- ProjectID: "test-project",
- CloudCred: true,
- WithoutAuth: false,
- ServiceAccount: "",
- },
- wantErr: true,
- },
- {
- name: "no auth method selected",
- gcsConfig: sources.GCSConfig{
- ProjectID: "test-project",
- MaxObjectSize: 10 * 1024 * 1024,
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ctx, cancel := context.WithCancel(context.TODO())
- defer cancel()
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
-
- conf := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: defaults.DefaultDetectors(),
- Verify: false,
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- e, err := NewEngine(ctx, &conf)
- assert.NoError(t, err)
-
- e.Start(ctx)
-
- go func() {
- resultCount := 0
- for range e.ResultsChan() {
- resultCount++
- }
- }()
-
- _, err = e.ScanGCS(ctx, test.gcsConfig)
- if err != nil && !test.wantErr && !strings.Contains(err.Error(), "googleapi: Error 400: Bad Request") {
- t.Errorf("ScanGCS() got: %v, want: %v", err, nil)
- return
- }
- if err := e.Finish(ctx); err != nil && !test.wantErr && !strings.Contains(err.Error(), "googleapi: Error 400: Bad Request") {
- t.Errorf("Finish() got: %v, want: %v", err, nil)
- return
- }
-
- if err == nil && test.wantErr {
- t.Errorf("ScanGCS() got: %v, want: %v", err, "error")
- }
- })
- }
-}
diff --git a/pkg/engine/git.go b/pkg/engine/git.go
deleted file mode 100644
index e232312f42e4..000000000000
--- a/pkg/engine/git.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package engine
-
-import (
- "runtime"
-
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
-)
-
-// ScanGit scans any git source.
-func (e *Engine) ScanGit(ctx context.Context, c sources.GitConfig) (sources.JobProgressRef, error) {
- connection := &sourcespb.Git{
- Head: c.HeadRef,
- Base: c.BaseRef,
- Bare: c.Bare,
- Uri: c.URI,
- ExcludeGlobs: c.ExcludeGlobs,
- IncludePathsFile: c.IncludePathsFile,
- ExcludePathsFile: c.ExcludePathsFile,
- MaxDepth: int64(c.MaxDepth),
- SkipBinaries: c.SkipBinaries,
- ClonePath: c.ClonePath,
- NoCleanup: c.NoCleanup,
- PrintLegacyJson: c.PrintLegacyJSON,
- TrustLocalGitConfig: c.TrustLocalGitConfig,
- }
-
- var conn anypb.Any
- if err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{}); err != nil {
- ctx.Logger().Error(err, "failed to marshal git connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - git"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, git.SourceType)
-
- gitSource := &git.Source{}
- if err := gitSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
- return sources.JobProgressRef{}, err
- }
-
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, gitSource)
-}
diff --git a/pkg/engine/git_test.go b/pkg/engine/git_test.go
deleted file mode 100644
index e1b6ce51d9da..000000000000
--- a/pkg/engine/git_test.go
+++ /dev/null
@@ -1,240 +0,0 @@
-package engine
-
-import (
- "net/url"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
- "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults"
- "github.com/trufflesecurity/trufflehog/v3/pkg/feature"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
-)
-
-type expResult struct {
- B string
- LineNumber int64
- Verified bool
-}
-
-type discardPrinter struct{}
-
-func (p *discardPrinter) Print(context.Context, *detectors.ResultWithMetadata) error {
- // This method intentionally does nothing.
- return nil
-}
-
-func TestGitEngine(t *testing.T) {
- ctx := context.Background()
- repoUrl := "https://github.com/dustin-decker/secretsandstuff.git"
- path, _, err := git.PrepareRepo(ctx, repoUrl, "", false, false)
- if err != nil {
- t.Error(err)
- }
- defer os.RemoveAll(path)
-
- ctx, cancel := context.WithCancel(ctx)
- defer cancel()
- type testProfile struct {
- expected map[string]expResult
- branch string
- base string
- maxDepth int
- }
- for tName, tTest := range map[string]testProfile{
- "all_secrets": {
- expected: map[string]expResult{
- "70001020fab32b1fcf2f1f0e5c66424eae649826": {"AKIAXYZDQCEN4B6JSJQI", 2, true},
- "84e9c75e388ae3e866e121087ea2dd45a71068f2": {"AKIAILE3JG6KMS3HZGCA", 4, true},
- "8afb0ecd4998b1179e428db5ebbcdc8221214432": {"369963c1434c377428ca8531fbc46c0c43d037a0", 3, false},
- "27fbead3bf883cdb7de9d7825ed401f28f9398f1": {"ffc7e0f9400fb6300167009e42d2f842cd7956e2", 7, false},
- },
- },
- "base_commit": {
- expected: map[string]expResult{
- "70001020fab32b1fcf2f1f0e5c66424eae649826": {"AKIAXYZDQCEN4B6JSJQI", 2, true},
- },
- base: "2f251b8c1e72135a375b659951097ec7749d4af9",
- },
- } {
- t.Run(tName, func(t *testing.T) {
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
-
- conf := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: defaults.DefaultDetectors(),
- Verify: true,
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- e, err := NewEngine(ctx, &conf)
- assert.NoError(t, err)
-
- e.Start(ctx)
-
- cfg := sources.GitConfig{
- URI: path,
- HeadRef: tTest.branch,
- BaseRef: tTest.base,
- MaxDepth: tTest.maxDepth,
- }
- if _, err := e.ScanGit(ctx, cfg); err != nil {
- return
- }
-
- // Wait for all the chunks to be processed.
- assert.Nil(t, e.Finish(ctx))
- for result := range e.ResultsChan() {
- switch meta := result.SourceMetadata.GetData().(type) {
- case *source_metadatapb.MetaData_Git:
- if tTest.expected[meta.Git.Commit].B != string(result.Raw) {
- t.Errorf("%s: unexpected result. Got: %s, Expected: %s", tName, string(result.Raw), tTest.expected[meta.Git.Commit].B)
- }
- if tTest.expected[meta.Git.Commit].LineNumber != result.SourceMetadata.GetGit().Line {
- t.Errorf("%s: unexpected line number. Got: %d, Expected: %d", tName, result.SourceMetadata.GetGit().Line, tTest.expected[meta.Git.Commit].LineNumber)
- }
- if tTest.expected[meta.Git.Commit].Verified != result.Verified {
- t.Errorf("%s: unexpected verification. Got: %v, Expected: %v", tName, result.Verified, tTest.expected[meta.Git.Commit].Verified)
- }
- }
-
- }
- metrics := e.GetMetrics()
- assert.Equal(t, len(tTest.expected), int(metrics.VerifiedSecretsFound)+int(metrics.UnverifiedSecretsFound))
- })
- }
-}
-
-func TestGitEngineWithMirrorAndBareClones(t *testing.T) {
- ctx := context.Background()
-
- parent, err := os.MkdirTemp("", "trufflehog-test-keys-*")
- if err != nil {
- t.Fail()
- }
- defer os.RemoveAll(parent)
- localRepo := filepath.Join(parent, "test_keys.git")
- cloneCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
- defer cancel()
-
- // clone with --mirror and --bare from https://github.com/trufflesecurity/test_keys.git to local and then pass it in as a local path
- cloneCmd := exec.CommandContext(cloneCtx, "git", "clone", "--mirror", "--bare", "https://github.com/trufflesecurity/test_keys.git", localRepo)
- if _, err := cloneCmd.CombinedOutput(); err != nil {
- t.Fail()
- }
-
- fileURI := (&url.URL{Scheme: "file", Path: filepath.ToSlash(localRepo)}).String()
-
- run := func(t *testing.T, mirror bool, cfg sources.GitConfig) (uint64, uint64) {
- t.Helper()
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
- sourceManager := sources.NewManager(opts...)
-
- conf := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: defaults.DefaultDetectors(),
- Verify: false, // avoid network-dependent verification in tests
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- feature.UseGitMirror.Store(false)
- if mirror {
- feature.UseGitMirror.Store(true)
- defer feature.UseGitMirror.Store(false)
- }
-
- e, err := NewEngine(ctx, &conf)
- assert.NoError(t, err)
-
- e.Start(ctx)
- _, err = e.ScanGit(ctx, cfg)
- assert.NoError(t, err)
- assert.NoError(t, e.Finish(ctx))
-
- m := e.GetMetrics()
- secrets := m.VerifiedSecretsFound + m.UnverifiedSecretsFound
- bytes := m.BytesScanned
- return secrets, bytes
- }
-
- s1, b1 := run(t, true, sources.GitConfig{URI: "https://github.com/trufflesecurity/test_keys.git"})
- s2, b2 := run(t, false, sources.GitConfig{URI: fileURI, Bare: true})
- s3, b3 := run(t, false, sources.GitConfig{URI: fileURI, Bare: true, TrustLocalGitConfig: true})
-
- assert.Greater(t, int(s1), 0)
- assert.Greater(t, int(b1), 0)
-
- assert.Equal(t, s1, s2)
- assert.Equal(t, s1, s3)
- assert.Equal(t, b1, b2)
- assert.Equal(t, b1, b3)
-}
-
-func BenchmarkGitEngine(b *testing.B) {
- ctx := context.Background()
- repoUrl := "https://github.com/dustin-decker/secretsandstuff.git"
-
- ctx, cancel := context.WithCancel(ctx)
- defer cancel()
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
-
- conf := Config{
- Concurrency: runtime.NumCPU(),
- Decoders: decoders.DefaultDecoders(),
- Detectors: defaults.DefaultDetectors(),
- Verify: false,
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- e, err := NewEngine(ctx, &conf)
- assert.NoError(b, err)
-
- go func() {
- resultCount := 0
- for range e.ResultsChan() {
- resultCount++
- }
- }()
-
- cfg := sources.GitConfig{URI: repoUrl}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- if _, err := e.ScanGit(ctx, cfg); err != nil {
- return
- }
- }
- assert.Nil(b, e.Finish(ctx))
-}
diff --git a/pkg/engine/github.go b/pkg/engine/github.go
deleted file mode 100644
index 1e0c21ec35ff..000000000000
--- a/pkg/engine/github.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package engine
-
-import (
- gogit "github.com/go-git/go-git/v5"
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/github"
-)
-
-// ScanGitHub scans GitHub with the provided options.
-func (e *Engine) ScanGitHub(ctx context.Context, c sources.GithubConfig) (sources.JobProgressRef, error) {
- connection := sourcespb.GitHub{
- Endpoint: c.Endpoint,
- Organizations: c.Orgs,
- Repositories: c.Repos,
- ScanUsers: c.IncludeMembers,
- IgnoreRepos: c.ExcludeRepos,
- IncludeForks: c.IncludeForks,
- IncludeIssueComments: c.IncludeIssueComments,
- IncludePullRequestComments: c.IncludePullRequestComments,
- IncludeGistComments: c.IncludeGistComments,
- IncludeWikis: c.IncludeWikis,
- SkipBinaries: c.SkipBinaries,
- CommentsTimeframeDays: c.CommentsTimeframeDays,
- RemoveAuthInUrl: !c.AuthInUrl, // configuration uses the opposite field in proto to keep credentials in the URL by default.
- ClonePath: c.ClonePath,
- NoCleanup: c.NoCleanup,
- IgnoreGists: c.IgnoreGists,
- PrintLegacyJson: c.PrintLegacyJSON,
- }
-
- if len(c.Token) > 0 {
- connection.Credential = &sourcespb.GitHub_Token{
- Token: c.Token,
- }
- } else {
- connection.Credential = &sourcespb.GitHub_Unauthenticated{}
- }
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, &connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal github connection")
- return sources.JobProgressRef{}, err
- }
-
- logOptions := &gogit.LogOptions{}
- opts := []git.ScanOption{
- git.ScanOptionFilter(c.Filter),
- git.ScanOptionLogOptions(logOptions),
- }
- scanOptions := git.NewScanOptions(opts...)
-
- sourceName := "trufflehog - github"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, github.SourceType)
-
- githubSource := &github.Source{}
- if err := githubSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, c.Concurrency); err != nil {
- return sources.JobProgressRef{}, err
- }
- githubSource.WithScanOptions(scanOptions)
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, githubSource)
-}
diff --git a/pkg/engine/github_experimental.go b/pkg/engine/github_experimental.go
deleted file mode 100644
index 0354a3637e27..000000000000
--- a/pkg/engine/github_experimental.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package engine
-
-import (
- "fmt"
- "runtime"
-
- gogit "github.com/go-git/go-git/v5"
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/github"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/github_experimental"
-)
-
-// ScanGitHubExperimental scans GitHub using an experimental feature. Consider all functionality to be in an alpha release here.
-func (e *Engine) ScanGitHubExperimental(ctx context.Context, c sources.GitHubExperimentalConfig) (sources.JobProgressRef, error) {
- connection := sourcespb.GitHubExperimental{
- Repository: c.Repository,
- ObjectDiscovery: c.ObjectDiscovery,
- CollisionThreshold: int64(c.CollisionThreshold),
- DeleteCachedData: c.DeleteCachedData,
- }
-
- // Check at least one experimental sub-module is being used.
- // Add to this list as more experimental sub-modules are added.
- if !c.ObjectDiscovery {
- return sources.JobProgressRef{}, fmt.Errorf("at least one experimental submodule must be enabled")
- }
-
- if len(c.Token) > 0 {
- connection.Credential = &sourcespb.GitHubExperimental_Token{
- Token: c.Token,
- }
- } else {
- return sources.JobProgressRef{}, fmt.Errorf("token is required for github experimental")
- }
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, &connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal github experimental connection")
- return sources.JobProgressRef{}, err
- }
-
- logOptions := &gogit.LogOptions{}
- opts := []git.ScanOption{
- git.ScanOptionLogOptions(logOptions),
- }
- scanOptions := git.NewScanOptions(opts...)
-
- sourceName := "trufflehog - github experimental (alpha release)"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, github.SourceType)
-
- githubExperimentalSource := &github_experimental.Source{}
- if err := githubExperimentalSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
- return sources.JobProgressRef{}, err
- }
- githubExperimentalSource.WithScanOptions(scanOptions)
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, githubExperimentalSource)
-}
diff --git a/pkg/engine/gitlab.go b/pkg/engine/gitlab.go
deleted file mode 100644
index 810057058ed2..000000000000
--- a/pkg/engine/gitlab.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package engine
-
-import (
- "fmt"
- "runtime"
-
- gogit "github.com/go-git/go-git/v5"
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/gitlab"
-)
-
-// ScanGitLab scans GitLab with the provided configuration.
-func (e *Engine) ScanGitLab(ctx context.Context, c sources.GitlabConfig) (sources.JobProgressRef, error) {
- logOptions := &gogit.LogOptions{}
- opts := []git.ScanOption{
- git.ScanOptionFilter(c.Filter),
- git.ScanOptionLogOptions(logOptions),
- }
- scanOptions := git.NewScanOptions(opts...)
-
- connection := &sourcespb.GitLab{
- SkipBinaries: c.SkipBinaries,
- RemoveAuthInUrl: !c.AuthInUrl, // configuration uses the opposite field in proto to keep credentials in the URL by default.
- }
-
- switch {
- case len(c.Token) > 0:
- connection.Credential = &sourcespb.GitLab_Token{
- Token: c.Token,
- }
- default:
- return sources.JobProgressRef{}, fmt.Errorf("must provide token")
- }
-
- if len(c.Endpoint) > 0 {
- connection.Endpoint = c.Endpoint
- }
-
- if len(c.Repos) > 0 {
- connection.Repositories = c.Repos
- }
-
- if len(c.GroupIds) > 0 {
- connection.GroupIds = c.GroupIds
- }
-
- if len(c.IncludeRepos) > 0 {
- connection.IncludeRepos = c.IncludeRepos
- }
-
- if len(c.ExcludeRepos) > 0 {
- connection.IgnoreRepos = c.ExcludeRepos
- }
-
- if c.ClonePath != "" {
- connection.ClonePath = c.ClonePath
- }
-
- connection.NoCleanup = c.NoCleanup
-
- connection.PrintLegacyJson = c.PrintLegacyJSON
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal gitlab connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - gitlab"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, gitlab.SourceType)
-
- gitlabSource := &gitlab.Source{}
- if err := gitlabSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
- return sources.JobProgressRef{}, err
- }
- gitlabSource.WithScanOptions(scanOptions)
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, gitlabSource)
-}
diff --git a/pkg/engine/gitlab_integration_test.go b/pkg/engine/gitlab_integration_test.go
deleted file mode 100644
index 5cc33cd5f69b..000000000000
--- a/pkg/engine/gitlab_integration_test.go
+++ /dev/null
@@ -1,43 +0,0 @@
-//go:build integration
-// +build integration
-
-package engine
-
-import (
- "fmt"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-func TestGitLab(t *testing.T) {
- // Run the scan.
- ctx := context.Background()
- e, err := NewEngine(ctx, &Config{
- Detectors: DefaultDetectors(),
- SourceManager: sources.NewManager(),
- Verify: false,
- })
- assert.NoError(t, err)
- e.Start(ctx)
-
- secret, err := common.GetTestSecret(ctx)
- if err != nil {
- t.Fatal(fmt.Errorf("failed to access secret: %v", err))
- }
- _, err = e.ScanGitLab(ctx, sources.GitlabConfig{
- Token: secret.MustGetField("GITLAB_TOKEN"),
- })
- assert.NoError(t, err)
-
- err = e.Finish(ctx)
- assert.NoError(t, err)
-
- // Check the output provided by metrics.
- metrics := e.GetMetrics()
- assert.GreaterOrEqual(t, metrics.ChunksScanned, uint64(36312))
- assert.GreaterOrEqual(t, metrics.BytesScanned, uint64(91618854))
-}
diff --git a/pkg/engine/huggingface.go b/pkg/engine/huggingface.go
deleted file mode 100644
index 9a20fe27f713..000000000000
--- a/pkg/engine/huggingface.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package engine
-
-import (
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/huggingface"
-)
-
-// HuggingFaceConfig represents the configuration for HuggingFace.
-type HuggingfaceConfig struct {
- Endpoint string
- Models []string
- Spaces []string
- Datasets []string
- Organizations []string
- Users []string
- IncludeModels []string
- IgnoreModels []string
- IncludeSpaces []string
- IgnoreSpaces []string
- IncludeDatasets []string
- IgnoreDatasets []string
- SkipAllModels bool
- SkipAllSpaces bool
- SkipAllDatasets bool
- IncludeDiscussions bool
- IncludePrs bool
- Token string
- Concurrency int
-}
-
-// ScanGitHub scans HuggingFace with the provided options.
-func (e *Engine) ScanHuggingface(ctx context.Context, c HuggingfaceConfig) (sources.JobProgressRef, error) {
- connection := sourcespb.Huggingface{
- Endpoint: c.Endpoint,
- Models: c.Models,
- Spaces: c.Spaces,
- Datasets: c.Datasets,
- Organizations: c.Organizations,
- Users: c.Users,
- IncludeModels: c.IncludeModels,
- IgnoreModels: c.IgnoreModels,
- IncludeSpaces: c.IncludeSpaces,
- IgnoreSpaces: c.IgnoreSpaces,
- IncludeDatasets: c.IncludeDatasets,
- IgnoreDatasets: c.IgnoreDatasets,
- SkipAllModels: c.SkipAllModels,
- SkipAllSpaces: c.SkipAllSpaces,
- SkipAllDatasets: c.SkipAllDatasets,
- IncludeDiscussions: c.IncludeDiscussions,
- IncludePrs: c.IncludePrs,
- }
- if len(c.Token) > 0 {
- connection.Credential = &sourcespb.Huggingface_Token{
- Token: c.Token,
- }
- } else {
- connection.Credential = &sourcespb.Huggingface_Unauthenticated{}
- }
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, &connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal huggingface connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - huggingface"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, sourcespb.SourceType_SOURCE_TYPE_HUGGINGFACE)
-
- huggingfaceSource := &huggingface.Source{}
- if err := huggingfaceSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, c.Concurrency); err != nil {
- return sources.JobProgressRef{}, err
- }
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, huggingfaceSource)
-}
diff --git a/pkg/engine/jenkins.go b/pkg/engine/jenkins.go
deleted file mode 100644
index b488951a2627..000000000000
--- a/pkg/engine/jenkins.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package engine
-
-import (
- "errors"
- "runtime"
- "strings"
-
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/jenkins"
-)
-
-type JenkinsConfig struct {
- Endpoint string
- Username string
- Password string
- Header string
- InsecureSkipVerifyTLS bool
-}
-
-// ScanJenkins scans Jenkins logs.
-func (e *Engine) ScanJenkins(ctx context.Context, jenkinsConfig JenkinsConfig) (sources.JobProgressRef, error) {
- var connection *sourcespb.Jenkins
- switch {
- case jenkinsConfig.Username != "" && jenkinsConfig.Password != "":
- connection = &sourcespb.Jenkins{
- Credential: &sourcespb.Jenkins_BasicAuth{
- BasicAuth: &credentialspb.BasicAuth{
- Username: jenkinsConfig.Username,
- Password: jenkinsConfig.Password,
- },
- },
- }
- case jenkinsConfig.Header != "":
- splits := strings.Split(jenkinsConfig.Header, ":")
- if len(splits) != 2 {
- return sources.JobProgressRef{}, errors.New("invalid header format, expected key: value")
- }
- key := splits[0]
- value := splits[1]
-
- connection = &sourcespb.Jenkins{
- Credential: &sourcespb.Jenkins_Header{
- Header: &credentialspb.Header{
- Key: key,
- Value: value,
- },
- },
- }
- default:
- connection = &sourcespb.Jenkins{
- Credential: &sourcespb.Jenkins_Unauthenticated{
- Unauthenticated: &credentialspb.Unauthenticated{},
- },
- }
- }
-
- connection.Endpoint = jenkinsConfig.Endpoint
- connection.InsecureSkipVerifyTls = jenkinsConfig.InsecureSkipVerifyTLS
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal Jenkins connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - Jenkins"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, jenkins.SourceType)
-
- jenkinsSource := &jenkins.Source{}
- if err := jenkinsSource.Init(ctx, "trufflehog - Jenkins", jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
- return sources.JobProgressRef{}, err
- }
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, jenkinsSource)
-}
diff --git a/pkg/engine/metrics.go b/pkg/engine/metrics.go
deleted file mode 100644
index 212061ca99ad..000000000000
--- a/pkg/engine/metrics.go
+++ /dev/null
@@ -1,117 +0,0 @@
-package engine
-
-import (
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promauto"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
-)
-
-var (
- decodeLatency = promauto.NewHistogramVec(
- prometheus.HistogramOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "decode_latency",
- Help: "Time spent decoding a chunk in microseconds",
- Buckets: prometheus.ExponentialBuckets(50, 2, 20),
- },
- []string{"decoder_type", "source_name"},
- )
-
- // Detector metrics.
- detectorExecutionCount = promauto.NewCounterVec(
- prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "detector_execution_count",
- Help: "Total number of times a detector has been executed.",
- },
- []string{"detector_name", "job_id", "source_name"},
- )
-
- // Note this is the time taken to call FromData on each detector, not necessarily the time taken
- // to verify a credential via an API call. If the regex match within FromData does not match, the
- // detector will return early. For now this is a good proxy for the time taken to verify a credential.
- // TODO (ahrav)
- // We can work on a more fine-grained metric later.
- detectorExecutionDuration = promauto.NewHistogramVec(
- prometheus.HistogramOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "detector_execution_duration",
- Help: "Duration of detector execution in milliseconds.",
- Buckets: prometheus.ExponentialBuckets(1, 5, 6),
- },
- []string{"detector_name"},
- )
-
- jobBytesScanned = promauto.NewCounterVec(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "job_bytes_scanned",
- Help: "Total number of bytes scanned for a job.",
- },
- []string{"source_type", "source_name"},
- )
-
- scanBytesPerChunk = promauto.NewHistogramVec(prometheus.HistogramOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "scan_bytes_per_chunk",
- Help: "Total number of bytes in a chunk.",
- Buckets: prometheus.ExponentialBuckets(1, 2, 18),
- },
- []string{"source_type"},
- )
-
- jobChunksScanned = promauto.NewCounterVec(prometheus.CounterOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "job_chunks_scanned",
- Help: "Total number of chunks scanned for a job.",
- },
- []string{"source_type", "source_name"},
- )
-
- detectBytesPerMatch = promauto.NewHistogram(prometheus.HistogramOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "detect_bytes_per_match",
- Help: "Total number of bytes used to detect a credential in a match per chunk.",
- Buckets: prometheus.ExponentialBuckets(1, 2, 18),
- })
-
- matchesPerChunk = promauto.NewHistogram(prometheus.HistogramOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "matches_per_chunk",
- Help: "Total number of matches found in a chunk.",
- Buckets: prometheus.ExponentialBuckets(1, 2, 10),
- })
-
- // Metrics around latency for the different stages of the pipeline.
- chunksScannedLatency = promauto.NewHistogram(prometheus.HistogramOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "chunk_scanned_latency",
- Help: "Time taken to scan a chunk in microseconds.",
- Buckets: prometheus.ExponentialBuckets(1, 2, 22),
- })
-
- chunksDetectedLatency = promauto.NewHistogram(prometheus.HistogramOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "chunk_detected_latency",
- Help: "Time taken to detect a chunk in microseconds.",
- Buckets: prometheus.ExponentialBuckets(50, 2, 20),
- })
-
- chunksNotifiedLatency = promauto.NewHistogram(prometheus.HistogramOpts{
- Namespace: common.MetricsNamespace,
- Subsystem: common.MetricsSubsystem,
- Name: "chunk_notified_latency",
- Help: "Time taken to notify a chunk in milliseconds.",
- Buckets: prometheus.ExponentialBuckets(5, 2, 12),
- })
-)
diff --git a/pkg/engine/postman.go b/pkg/engine/postman.go
deleted file mode 100644
index fc5e6ae080b9..000000000000
--- a/pkg/engine/postman.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package engine
-
-import (
- "errors"
-
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/postman"
-)
-
-// ScanPostman scans Postman with the provided options.
-func (e *Engine) ScanPostman(ctx context.Context, c sources.PostmanConfig) (sources.JobProgressRef, error) {
- connection := sourcespb.Postman{
- Workspaces: c.Workspaces,
- Collections: c.Collections,
- Environments: c.Environments,
- IncludeCollections: c.IncludeCollections,
- IncludeEnvironments: c.IncludeEnvironments,
- ExcludeCollections: c.ExcludeCollections,
- ExcludeEnvironments: c.ExcludeEnvironments,
- WorkspacePaths: c.WorkspacePaths,
- CollectionPaths: c.CollectionPaths,
- EnvironmentPaths: c.EnvironmentPaths,
- }
-
- // Check if postman data is going to be accessed via an api call using a token, or
- // if it has been already exported and exists locally
- if len(c.Token) > 0 {
- connection.Credential = &sourcespb.Postman_Token{
- Token: c.Token,
- }
- } else if len(c.WorkspacePaths) > 0 || len(c.CollectionPaths) > 0 || len(c.EnvironmentPaths) > 0 {
- connection.Credential = &sourcespb.Postman_Unauthenticated{}
- } else {
- return sources.JobProgressRef{}, errors.New("no path to locally exported data or API token provided")
- }
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, &connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal Postman connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - postman"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, postman.SourceType)
-
- postmanSource := &postman.Source{
- DetectorKeywords: e.AhoCorasickCoreKeywords(),
- }
-
- if err := postmanSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, c.Concurrency); err != nil {
- return sources.JobProgressRef{}, err
- }
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, postmanSource)
-}
diff --git a/pkg/engine/postman_test.go b/pkg/engine/postman_test.go
deleted file mode 100644
index 081002f7cf5e..000000000000
--- a/pkg/engine/postman_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package engine
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
- "github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
-)
-
-func TestPostmanEngine(t *testing.T) {
- tests := []struct {
- name string
- postmanConfig sources.PostmanConfig
- wantErr bool
- }{
- {
- name: "scanned Postman with a token",
- postmanConfig: sources.PostmanConfig{
- Token: "dummy_key",
- },
- },
- {
- name: "scanned Postman with workspacePath",
- postmanConfig: sources.PostmanConfig{
- WorkspacePaths: []string{"Downloads/Test API.postman_collection.json"},
- },
- },
- {
- name: "scanned Postman with environmentPath",
- postmanConfig: sources.PostmanConfig{
- EnvironmentPaths: []string{"Downloads/Mobile - Points Unlock Redeemables.postman_environment.json"},
- },
- },
- {
- name: "no token or file path provided",
- wantErr: true,
- },
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ctx, cancel := context.WithCancel(context.TODO())
- defer cancel()
-
- const defaultOutputBufferSize = 64
- opts := []func(*sources.SourceManager){
- sources.WithSourceUnits(),
- sources.WithBufferedOutput(defaultOutputBufferSize),
- }
-
- sourceManager := sources.NewManager(opts...)
-
- conf := Config{
- Concurrency: 1,
- Decoders: decoders.DefaultDecoders(),
- Detectors: defaults.DefaultDetectors(),
- Verify: false,
- SourceManager: sourceManager,
- Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
- }
-
- e, err := NewEngine(ctx, &conf)
- assert.NoError(t, err)
- e.Start(ctx)
- _, err = e.ScanPostman(ctx, test.postmanConfig)
- if err != nil && !test.wantErr {
- t.Errorf("ScanPostman() got: %v, want: %v", err, nil)
- return
- }
- if err == nil && test.wantErr {
- t.Errorf("ScanPostman() got: %v, want: %v", err, "error")
- }
- })
- }
-}
diff --git a/pkg/engine/s3.go b/pkg/engine/s3.go
deleted file mode 100644
index 1f6fe5b6dada..000000000000
--- a/pkg/engine/s3.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package engine
-
-import (
- "fmt"
- "runtime"
-
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/s3"
-)
-
-// ScanS3 scans S3 buckets.
-func (e *Engine) ScanS3(ctx context.Context, c sources.S3Config) (sources.JobProgressRef, error) {
- connection := &sourcespb.S3{
- Credential: &sourcespb.S3_Unauthenticated{},
- }
- if c.CloudCred {
- if len(c.Key) > 0 || len(c.Secret) > 0 || len(c.SessionToken) > 0 {
- return sources.JobProgressRef{}, fmt.Errorf("cannot use cloud environment and static credentials together")
- }
- connection.Credential = &sourcespb.S3_CloudEnvironment{}
- }
- if len(c.Key) > 0 && len(c.Secret) > 0 {
- if len(c.SessionToken) > 0 {
- connection.Credential = &sourcespb.S3_SessionToken{
- SessionToken: &credentialspb.AWSSessionTokenSecret{
- Key: c.Key,
- Secret: c.Secret,
- SessionToken: c.SessionToken,
- },
- }
- } else {
- connection.Credential = &sourcespb.S3_AccessKey{
- AccessKey: &credentialspb.KeySecret{
- Key: c.Key,
- Secret: c.Secret,
- },
- }
- }
- }
- if len(c.Buckets) > 0 {
- connection.Buckets = c.Buckets
- }
- if len(c.IgnoreBuckets) > 0 {
- connection.IgnoreBuckets = c.IgnoreBuckets
- }
-
- if len(c.Roles) > 0 {
- connection.Roles = c.Roles
- }
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal S3 connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - s3"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, s3.SourceType)
-
- s3Source := &s3.Source{}
- if err := s3Source.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
- return sources.JobProgressRef{}, err
- }
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, s3Source)
-}
diff --git a/pkg/engine/scan.go b/pkg/engine/scan.go
deleted file mode 100644
index 167204e2a3c6..000000000000
--- a/pkg/engine/scan.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package engine
-
-import (
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/postman"
-)
-
-// ScanConfig starts a scan of all of the configured (but not initialized)
-// sources and returns their job references. If there is an error during
-// initialization or starting of the scan, an error is returned along with the
-// references that successfully started up to that point.
-func (e *Engine) ScanConfig(ctx context.Context, configuredSources ...sources.ConfiguredSource) ([]sources.JobProgressRef, error) {
- var refs []sources.JobProgressRef
- for _, configuredSource := range configuredSources {
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, configuredSource.Name, configuredSource.SourceType())
-
- source, err := configuredSource.Init(ctx, sourceID, jobID)
- if err != nil {
- return refs, err
- }
- // Postman needs special initialization to set Keywords from
- // the engine.
- if postmanSource, ok := source.(*postman.Source); ok {
- postmanSource.DetectorKeywords = e.AhoCorasickCoreKeywords()
- }
-
- // Start the scan.
- ref, err := e.sourceManager.EnumerateAndScan(ctx, configuredSource.Name, source)
- if err != nil {
- return refs, err
- }
- refs = append(refs, ref)
- }
- return refs, nil
-}
diff --git a/pkg/engine/stdin.go b/pkg/engine/stdin.go
deleted file mode 100644
index 65e336241e69..000000000000
--- a/pkg/engine/stdin.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package engine
-
-import (
- "runtime"
-
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/stdin"
-)
-
-// ScanStdinInput scans input that is piped into the application
-func (e *Engine) ScanStdinInput(ctx context.Context, c sources.StdinConfig) (sources.JobProgressRef, error) {
- connection := &sourcespb.Stdin{}
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal stdin connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - stdin"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, stdin.SourceType)
-
- stdinSource := &stdin.Source{}
- if err := stdinSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
- return sources.JobProgressRef{}, err
- }
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, stdinSource)
-}
diff --git a/pkg/engine/syslog.go b/pkg/engine/syslog.go
deleted file mode 100644
index a857fd90cecf..000000000000
--- a/pkg/engine/syslog.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package engine
-
-import (
- "os"
-
- "github.com/go-errors/errors"
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/syslog"
-)
-
-// ScanSyslog is a source that scans syslog files.
-func (e *Engine) ScanSyslog(ctx context.Context, c sources.SyslogConfig) (sources.JobProgressRef, error) {
- connection := &sourcespb.Syslog{
- Protocol: c.Protocol,
- ListenAddress: c.Address,
- Format: c.Format,
- }
-
- if c.CertPath != "" && c.KeyPath != "" {
- cert, err := os.ReadFile(c.CertPath)
- if err != nil {
- return sources.JobProgressRef{}, errors.WrapPrefix(err, "could not open TLS cert file", 0)
- }
- connection.TlsCert = string(cert)
-
- key, err := os.ReadFile(c.KeyPath)
- if err != nil {
- return sources.JobProgressRef{}, errors.WrapPrefix(err, "could not open TLS key file", 0)
- }
- connection.TlsKey = string(key)
- }
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
- if err != nil {
- return sources.JobProgressRef{}, errors.WrapPrefix(err, "error unmarshalling connection", 0)
- }
-
- sourceName := "trufflehog - syslog"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, syslog.SourceType)
- syslogSource := &syslog.Source{}
- if err := syslogSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, c.Concurrency); err != nil {
- return sources.JobProgressRef{}, err
- }
- syslogSource.InjectConnection(connection)
-
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, syslogSource)
-}
diff --git a/pkg/engine/testdata/secrets.txt b/pkg/engine/testdata/secrets.txt
deleted file mode 100644
index ed277e51f408..000000000000
--- a/pkg/engine/testdata/secrets.txt
+++ /dev/null
@@ -1,6 +0,0 @@
- 1 aws AKIAWARWQKZNHMZBLY4I
- 2 secret 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
- sentry 27ac84f4bcdb4fca9701f4d6f6f58cd7d96b69c9d9754d40800645a51d668f90
- sentry 27ac84f4bcdb4fca9701f4d6f6f58cd7d96b69c9d9754d40800645a51d668f90
- sentry 27ac84f4bcdb4fca9701f4d6f6f58cd7d96b69c9d9754d40800645a51d668f90
- sentry 27ac84f4bcdb4fca9701f4d6f6f58cd7d96b69c9d9754d40800645a51d668f90
\ No newline at end of file
diff --git a/pkg/engine/testdata/verificationoverlap_detectors.yaml b/pkg/engine/testdata/verificationoverlap_detectors.yaml
deleted file mode 100644
index 583a12f69d36..000000000000
--- a/pkg/engine/testdata/verificationoverlap_detectors.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-# config.yaml
-detectors:
- - name: detector1
- keywords:
- - PMAK
- regex:
- api_key: \b(PMAK-[a-zA-Z-0-9]{59})\b
-
- - name: detector2
- keywords:
- - ost
- regex:
- api_key: \b([a-zA-Z-0-9]{59})\b
\ No newline at end of file
diff --git a/pkg/engine/testdata/verificationoverlap_detectors_fp.yaml b/pkg/engine/testdata/verificationoverlap_detectors_fp.yaml
deleted file mode 100644
index 5e4403a7c51c..000000000000
--- a/pkg/engine/testdata/verificationoverlap_detectors_fp.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-# config.yaml
-detectors:
- - name: detector1
- keywords:
- - sample
- regex:
- api_key: \b(sample-[a-zA-Z-0-9]{59})\b
-
- - name: detector2
- keywords:
- - ample
- regex:
- api_key: \b(ssample-[a-zA-Z-0-9]{59})\b
\ No newline at end of file
diff --git a/pkg/engine/testdata/verificationoverlap_secrets.txt b/pkg/engine/testdata/verificationoverlap_secrets.txt
deleted file mode 100644
index 30eff11c9f33..000000000000
--- a/pkg/engine/testdata/verificationoverlap_secrets.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-
-POSTMAN_API_KEY="PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r"
diff --git a/pkg/engine/testdata/verificationoverlap_secrets_fp.txt b/pkg/engine/testdata/verificationoverlap_secrets_fp.txt
deleted file mode 100644
index 03c34d47861d..000000000000
--- a/pkg/engine/testdata/verificationoverlap_secrets_fp.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-
-POSTMAN_API_KEY="ssample-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r"
diff --git a/pkg/engine/travisci.go b/pkg/engine/travisci.go
deleted file mode 100644
index 790ced12d838..000000000000
--- a/pkg/engine/travisci.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package engine
-
-import (
- "runtime"
-
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/known/anypb"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources"
- "github.com/trufflesecurity/trufflehog/v3/pkg/sources/travisci"
-)
-
-// ScanTravisCI scans TravisCI logs.
-func (e *Engine) ScanTravisCI(ctx context.Context, token string) (sources.JobProgressRef, error) {
- connection := &sourcespb.TravisCI{
- Credential: &sourcespb.TravisCI_Token{
- Token: token,
- },
- }
-
- var conn anypb.Any
- err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
- if err != nil {
- ctx.Logger().Error(err, "failed to marshal Travis CI connection")
- return sources.JobProgressRef{}, err
- }
-
- sourceName := "trufflehog - Travis CI"
- sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, travisci.SourceType)
-
- travisSource := &travisci.Source{}
- if err := travisSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
- return sources.JobProgressRef{}, err
- }
- return e.sourceManager.EnumerateAndScan(ctx, sourceName, travisSource)
-}
diff --git a/pkg/feature/feature.go b/pkg/feature/feature.go
deleted file mode 100644
index 080788c0218c..000000000000
--- a/pkg/feature/feature.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package feature
-
-import (
- "sync/atomic"
-)
-
-var (
- ForceSkipBinaries atomic.Bool
- ForceSkipArchives atomic.Bool
- GitCloneTimeoutDuration atomic.Int64
- SkipAdditionalRefs atomic.Bool
- EnableAPKHandler atomic.Bool
- UserAgentSuffix AtomicString
- UseSimplifiedGitlabEnumeration atomic.Bool
- UseGitMirror atomic.Bool
- GitlabProjectsPerPage atomic.Int64
- UseGithubGraphQLAPI atomic.Bool // use github graphql api to fetch issues, pr's and comments
-)
-
-type AtomicString struct {
- value atomic.Value
-}
-
-// Load returns the current value of the atomic string
-func (as *AtomicString) Load() string {
- if v := as.value.Load(); v != nil {
- return v.(string)
- }
- return ""
-}
-
-// Store sets the value of the atomic string
-func (as *AtomicString) Store(newValue string) {
- as.value.Store(newValue)
-}
-
-// Swap atomically swaps the current string with a new one and returns the old value
-func (as *AtomicString) Swap(newValue string) string {
- oldValue := as.Load()
- as.Store(newValue)
- return oldValue
-}
diff --git a/pkg/gitparse/gitparse.go b/pkg/gitparse/gitparse.go
deleted file mode 100644
index 920fbbde6578..000000000000
--- a/pkg/gitparse/gitparse.go
+++ /dev/null
@@ -1,985 +0,0 @@
-package gitparse
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "os"
- "os/exec"
- "path/filepath"
- "strconv"
- "strings"
- "time"
-
- "github.com/go-logr/logr"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/common"
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- bufferwriter "github.com/trufflesecurity/trufflehog/v3/pkg/writers/buffer_writer"
- bufferedfilewriter "github.com/trufflesecurity/trufflehog/v3/pkg/writers/buffered_file_writer"
-)
-
-const (
- // defaultDateFormat is the standard date format for git.
- // Uses ISO 8601 format to avoid locale-dependent weekday/month names.
- defaultDateFormat = time.RFC3339
-
- // defaultMaxDiffSize is the maximum size for a diff. Larger diffs will be cut off.
- defaultMaxDiffSize int64 = 2 * 1024 * 1024 * 1024 // 2GB
-
- // defaultMaxCommitSize is the maximum size for a commit. Larger commits will be cut off.
- defaultMaxCommitSize int64 = 2 * 1024 * 1024 * 1024 // 2GB
-)
-
-// contentWriter defines a common interface for writing, reading, and managing diff content.
-// It abstracts the underlying storage mechanism, allowing flexibility in how content is handled.
-// This interface enables the use of different content storage strategies (e.g., in-memory buffer, file-based storage)
-// based on performance needs or resource constraints, providing a unified way to interact with different content types.
-type contentWriter interface { // Write appends data to the content storage.
- // Write appends data to the content storage.
- Write(data []byte) (int, error)
- // ReadCloser provides a reader for accessing stored content.
- ReadCloser() (io.ReadCloser, error)
- // CloseForWriting closes the content storage for writing.
- CloseForWriting() error
- // Len returns the current size of the content.
- Len() int
- // String returns the content as a string or an error if the content cannot be converted to a string.
- String() (string, error)
-}
-
-// Diff contains the information about a file diff in a commit.
-// It abstracts the underlying content representation, allowing for flexible handling of diff content.
-// The use of contentWriter enables the management of diff data either in memory or on disk,
-// based on its size, optimizing resource usage and performance.
-type Diff struct {
- PathB string
- LineStart int
- IsBinary bool
-
- Commit *Commit
-
- contentWriter contentWriter
-}
-
-type diffOption func(*Diff)
-
-// withPathB sets the PathB option.
-func withPathB(pathB string) diffOption { return func(d *Diff) { d.PathB = pathB } }
-
-// withCustomContentWriter sets the useCustomContentWriter option.
-func withCustomContentWriter(cr contentWriter) diffOption {
- return func(d *Diff) { d.contentWriter = cr }
-}
-
-// newDiff creates a new Diff with a threshold and an associated commit.
-// All Diffs must have an associated commit.
-// The contentWriter is used to manage the diff's content, allowing for flexible handling of diff data.
-// By default, a buffer is used as the contentWriter, but this can be overridden with a custom contentWriter.
-func newDiff(commit *Commit, opts ...diffOption) *Diff {
- diff := &Diff{Commit: commit}
- for _, opt := range opts {
- opt(diff)
- }
-
- if diff.contentWriter == nil {
- diff.contentWriter = bufferwriter.New()
- }
-
- return diff
-}
-
-// Len returns the length of the storage.
-func (d *Diff) Len() int { return d.contentWriter.Len() }
-
-// ReadCloser returns a ReadCloser for the contentWriter.
-func (d *Diff) ReadCloser() (io.ReadCloser, error) { return d.contentWriter.ReadCloser() }
-
-// write delegates to the contentWriter.
-func (d *Diff) write(p []byte) error {
- _, err := d.contentWriter.Write(p)
- return err
-}
-
-// finalize ensures proper closure of resources associated with the Diff.
-// handle the final flush in the finalize method, in case there's data remaining in the buffer.
-// This method should be called to release resources, especially when writing to a file.
-func (d *Diff) finalize() error { return d.contentWriter.CloseForWriting() }
-
-// Commit contains commit header info and diffs.
-type Commit struct {
- Hash string
- Author string
- Committer string
- Date time.Time
- Message strings.Builder
- Size int // in bytes
-
- hasDiffs bool
-}
-
-// Parser sets values used in GitParse.
-type Parser struct {
- maxDiffSize int64
- maxCommitSize int64
- dateFormat string
-
- useCustomContentWriter bool
-}
-
-type ParseState int
-
-const (
- Initial ParseState = iota
- CommitLine
- MergeLine
- AuthorLine
- AuthorDateLine
- CommitterLine
- CommitterDateLine
- MessageStartLine
- MessageLine
- MessageEndLine
- NotesStartLine
- NotesLine
- NotesEndLine
- DiffLine
- ModeLine
- IndexLine
- FromFileLine
- ToFileLine
- BinaryFileLine
- HunkLineNumberLine
- HunkContentLine
- ParseFailure
-)
-
-func (state ParseState) String() string {
- return [...]string{
- "Initial",
- "CommitLine",
- "MergeLine",
- "AuthorLine",
- "AuthorDateLine",
- "CommitterLine",
- "CommitterDateLine",
- "MessageStartLine",
- "MessageLine",
- "MessageEndLine",
- "NotesStartLine",
- "NotesLine",
- "NotesEndLine",
- "DiffLine",
- "ModeLine",
- "IndexLine",
- "FromFileLine",
- "ToFileLine",
- "BinaryFileLine",
- "HunkLineNumberLine",
- "HunkContentLine",
- "ParseFailure",
- }[state]
-}
-
-// UseCustomContentWriter sets useCustomContentWriter option.
-func UseCustomContentWriter() Option {
- return func(parser *Parser) { parser.useCustomContentWriter = true }
-}
-
-// WithMaxDiffSize sets maxDiffSize option. Diffs larger than maxDiffSize will
-// be truncated.
-func WithMaxDiffSize(maxDiffSize int64) Option {
- return func(parser *Parser) {
- parser.maxDiffSize = maxDiffSize
- }
-}
-
-// WithMaxCommitSize sets maxCommitSize option. Commits larger than maxCommitSize
-// will be put in the commit channel and additional diffs will be added to a
-// new commit.
-func WithMaxCommitSize(maxCommitSize int64) Option {
- return func(parser *Parser) {
- parser.maxCommitSize = maxCommitSize
- }
-}
-
-// Option is used for adding options to Config.
-type Option func(*Parser)
-
-// NewParser creates a GitParse config from options and sets defaults.
-func NewParser(options ...Option) *Parser {
- parser := &Parser{
- dateFormat: defaultDateFormat,
- maxDiffSize: defaultMaxDiffSize,
- maxCommitSize: defaultMaxCommitSize,
- }
- for _, option := range options {
- option(parser)
- }
- return parser
-}
-
-// RepoPath parses the output of the `git log` command for the `source` path.
-// The Diff chan will return diffs in the order they are parsed from the log.
-func (c *Parser) RepoPath(
- ctx context.Context,
- source string,
- head string,
- abbreviatedLog bool,
- excludedGlobs []string,
- isBare bool,
- additionalArgs ...string,
-) (chan *Diff, error) {
- args := []string{
- "-C", source,
- "log",
- "--patch", // https://git-scm.com/docs/git-log#Documentation/git-log.txt---patch
- "--full-history",
- "--date=iso-strict",
- "--pretty=fuller", // https://git-scm.com/docs/git-log#_pretty_formats
- "--notes", // https://git-scm.com/docs/git-log#Documentation/git-log.txt---notesltrefgt
- }
- if abbreviatedLog {
- args = append(args, "--diff-filter=AM")
- }
- if head != "" {
- args = append(args, head)
- } else {
- args = append(args, "--all")
- }
- args = append(args, additionalArgs...) // These need to come before --
- for _, glob := range excludedGlobs {
- args = append(args, "--", ".", ":(exclude)"+glob)
- }
-
- cmd := exec.Command("git", args...)
- absPath, err := filepath.Abs(source)
- if err == nil {
- if !isBare {
- cmd.Env = append(cmd.Env, "GIT_DIR="+filepath.Join(absPath, ".git"))
- } else {
- cmd.Env = append(cmd.Env,
- "GIT_DIR="+absPath,
- )
- // We need those variables to handle incoming commits
- // while using trufflehog in pre-receive hooks
- if dir := os.Getenv("GIT_OBJECT_DIRECTORY"); dir != "" {
- cmd.Env = append(cmd.Env, "GIT_OBJECT_DIRECTORY="+dir)
- }
- if dir := os.Getenv("GIT_ALTERNATE_OBJECT_DIRECTORIES"); dir != "" {
- cmd.Env = append(cmd.Env, "GIT_ALTERNATE_OBJECT_DIRECTORIES="+dir)
- }
- }
- }
-
- return c.executeCommand(ctx, cmd, false)
-}
-
-// Staged parses the output of the `git diff` command for the `source` path.
-func (c *Parser) Staged(ctx context.Context, source string) (chan *Diff, error) {
- // Provide the --cached flag to diff to get the diff of the staged changes.
- args := []string{"-C", source, "diff", "-p", "--cached", "--full-history", "--diff-filter=AM", "--date=iso-strict"}
-
- cmd := exec.Command("git", args...)
-
- absPath, err := filepath.Abs(source)
- if err == nil {
- cmd.Env = append(cmd.Env, "GIT_DIR="+filepath.Join(absPath, ".git"))
- }
-
- return c.executeCommand(ctx, cmd, true)
-}
-
-// executeCommand runs an exec.Cmd, reads stdout and stderr, and waits for the Cmd to complete.
-func (c *Parser) executeCommand(ctx context.Context, cmd *exec.Cmd, isStaged bool) (chan *Diff, error) {
- diffChan := make(chan *Diff, 64)
-
- stdOut, err := cmd.StdoutPipe()
- if err != nil {
- return diffChan, err
- }
- stdErr, err := cmd.StderrPipe()
- if err != nil {
- return diffChan, err
- }
-
- err = cmd.Start()
- if err != nil {
- return diffChan, err
- }
-
- go func() {
- scanner := bufio.NewScanner(stdErr)
- for scanner.Scan() {
- ctx.Logger().V(2).Info(scanner.Text())
- }
- }()
-
- go func() {
- defer func() {
- if err := cmd.Wait(); err != nil {
- ctx.Logger().V(2).Info("Error waiting for git command to complete.", "error", err)
- }
- }()
- c.FromReader(ctx, stdOut, diffChan, isStaged)
- if err := stdOut.Close(); err != nil {
- ctx.Logger().V(2).Info("Error closing git stdout pipe.", "error", err)
- }
- }()
-
- return diffChan, nil
-}
-
-func (c *Parser) FromReader(ctx context.Context, stdOut io.Reader, diffChan chan *Diff, isStaged bool) {
- outReader := bufio.NewReader(stdOut)
- var (
- currentCommit *Commit
-
- totalLogSize int
- )
- var latestState = Initial
-
- diff := func(c *Commit, opts ...diffOption) *Diff {
- opts = append(opts, withCustomContentWriter(bufferwriter.New()))
- return newDiff(c, opts...)
- }
- if c.useCustomContentWriter {
- diff = func(c *Commit, opts ...diffOption) *Diff {
- opts = append(opts, withCustomContentWriter(bufferedfilewriter.New()))
- return newDiff(c, opts...)
- }
- }
- currentDiff := diff(currentCommit)
-
- defer common.RecoverWithExit(ctx)
- defer close(diffChan)
- for {
- if common.IsDone(ctx) {
- break
- }
-
- line, err := outReader.ReadBytes([]byte("\n")[0])
- if err != nil && len(line) == 0 {
- break
- }
-
- switch {
- case isCommitLine(isStaged, latestState, line):
- latestState = CommitLine
-
- // If there is a currentDiff, add it to currentCommit.
- if currentDiff.Len() > 0 || currentDiff.IsBinary {
- if err := currentDiff.finalize(); err != nil {
- ctx.Logger().Error(
- err,
- "failed to finalize diff",
- "commit", currentCommit.Hash,
- "diff", currentDiff.PathB,
- "size", currentDiff.Len(),
- "latest_state", latestState.String(),
- )
- }
- diffChan <- currentDiff
- currentCommit.Size += currentDiff.Len()
- currentCommit.hasDiffs = true
- }
- // If there is a currentCommit, send it to the channel.
- if currentCommit != nil {
- totalLogSize += currentCommit.Size
- if !currentCommit.hasDiffs {
- // Initialize an empty Diff instance associated with the given commit.
- // Since this diff represents "no changes", we only need to set the commit.
- // This is required to ensure commits that have no diffs are still processed.
- diffChan <- &Diff{Commit: currentCommit}
- }
- }
-
- // Create a new currentDiff and currentCommit
- currentCommit = &Commit{Message: strings.Builder{}}
- currentDiff = diff(currentCommit)
- // Check that the commit line contains a hash and set it.
- if len(line) >= 47 {
- currentCommit.Hash = string(line[7:47])
- }
- case isMergeLine(isStaged, latestState, line):
- latestState = MergeLine
- case isAuthorLine(isStaged, latestState, line):
- latestState = AuthorLine
- currentCommit.Author = strings.TrimSpace(string(line[8:]))
- case isAuthorDateLine(isStaged, latestState, line):
- latestState = AuthorDateLine
-
- date, err := time.Parse(c.dateFormat, strings.TrimSpace(string(line[12:])))
- if err != nil {
- ctx.Logger().Error(err, "failed to parse commit date", "commit", currentCommit.Hash, "latestState", latestState.String())
- latestState = ParseFailure
- continue
- }
- currentCommit.Date = date
- case isCommitterLine(isStaged, latestState, line):
- latestState = CommitterLine
- currentCommit.Committer = strings.TrimSpace(string(line[8:]))
- case isCommitterDateLine(isStaged, latestState, line):
- latestState = CommitterDateLine
- // NoOp
- case isMessageStartLine(isStaged, latestState, line):
- latestState = MessageStartLine
- // NoOp
- case isMessageLine(isStaged, latestState, line):
- latestState = MessageLine
- currentCommit.Message.Write(line[4:]) // Messages are indented by 4 spaces.
-
- case isMessageEndLine(isStaged, latestState, line):
- latestState = MessageEndLine
- // NoOp
- case isNotesStartLine(isStaged, latestState, line):
- latestState = NotesStartLine
-
- currentCommit.Message.WriteString("\n")
- currentCommit.Message.Write(line)
- case isNotesLine(isStaged, latestState, line):
- latestState = NotesLine
- currentCommit.Message.Write(line[4:]) // Notes are indented by 4 spaces.
- case isNotesEndLine(isStaged, latestState, line):
- latestState = NotesEndLine
- // NoOp
- case isDiffLine(isStaged, latestState, line):
- latestState = DiffLine
-
- if currentDiff.Len() > 0 || currentDiff.IsBinary {
- if err := currentDiff.finalize(); err != nil {
- ctx.Logger().Error(err,
- "failed to finalize diff",
- "commit", currentCommit.Hash,
- "diff", currentDiff.PathB,
- "size", currentDiff.Len(),
- "latest_state", latestState.String(),
- )
- }
- diffChan <- currentDiff
- currentCommit.hasDiffs = true
- }
-
- // This should never be nil, but check in case the stdin stream is messed up.
- if currentCommit == nil {
- currentCommit = &Commit{}
- }
- currentDiff = diff(currentCommit)
- case isModeLine(latestState, line):
- latestState = ModeLine
- // NoOp
- case isIndexLine(latestState, line):
- latestState = IndexLine
- // NoOp
- case isBinaryLine(latestState, line):
- latestState = BinaryFileLine
-
- path, ok := pathFromBinaryLine(line)
- if !ok {
- err = fmt.Errorf(`expected line to match 'Binary files a/fileA and b/fileB differ', got "%s"`, line)
- ctx.Logger().Error(err, "Failed to parse BinaryFileLine")
- latestState = ParseFailure
- continue
- }
-
- // Don't do anything if the file is deleted. (pathA has file path, pathB is /dev/null)
- if path != "" {
- currentDiff.PathB = path
- currentDiff.IsBinary = true
- }
- case isFromFileLine(latestState, line):
- latestState = FromFileLine
- // NoOp
- case isToFileLine(latestState, line):
- latestState = ToFileLine
-
- path, ok := pathFromToFileLine(line)
- if !ok {
- err = fmt.Errorf(`expected line to match format '+++ b/path/to/file.go', got '%s'`, line)
- ctx.Logger().Error(err, "Failed to parse ToFileLine")
- latestState = ParseFailure
- continue
- }
-
- currentDiff.PathB = path
- case isHunkLineNumberLine(latestState, line):
- latestState = HunkLineNumberLine
-
- if currentDiff.Len() > 0 || currentDiff.IsBinary {
- if err := currentDiff.finalize(); err != nil {
- ctx.Logger().Error(
- err,
- "failed to finalize diff",
- "commit", currentCommit.Hash,
- "diff", currentDiff.PathB,
- "size", currentDiff.Len(),
- "latest_state", latestState.String(),
- )
- }
- diffChan <- currentDiff
- }
- currentDiff = diff(currentCommit, withPathB(currentDiff.PathB))
-
- words := bytes.Split(line, []byte(" "))
- if len(words) >= 3 {
- startSlice := bytes.Split(words[2], []byte(","))
- lineStart, err := strconv.Atoi(string(startSlice[0]))
- if err == nil {
- currentDiff.LineStart = lineStart
- }
- }
- case isHunkContextLine(latestState, line):
- if latestState != HunkContentLine {
- latestState = HunkContentLine
- }
- // TODO: Why do we care about this? It creates empty lines in the diff. If there are no plusLines, it's just newlines.
- if err := currentDiff.write([]byte("\n")); err != nil {
- ctx.Logger().Error(err, "failed to write to diff")
- }
- case isHunkPlusLine(latestState, line):
- if latestState != HunkContentLine {
- latestState = HunkContentLine
- }
-
- if err := currentDiff.write(line[1:]); err != nil {
- ctx.Logger().Error(err, "failed to write to diff")
- }
- // NoOp. We only care about additions.
- case isHunkMinusLine(latestState, line),
- isHunkNewlineWarningLine(latestState, line),
- isHunkEmptyLine(latestState, line):
- if latestState != HunkContentLine {
- latestState = HunkContentLine
- }
- // NoOp
- case isCommitSeparatorLine(latestState, line):
- // NoOp
- default:
- // Skip ahead until we find the next diff or commit.
- if latestState == ParseFailure {
- continue
- }
-
- // Here be dragons...
- // Build an informative error message.
- err := fmt.Errorf(`invalid line "%s" after state "%s"`, line, latestState)
- var logger logr.Logger
- if currentCommit != nil && currentCommit.Hash != "" {
- logger = ctx.Logger().WithValues("commit", currentCommit.Hash)
- } else {
- logger = ctx.Logger()
- }
- logger.Error(err, "failed to parse Git input. Recovering at the latest commit or diff...")
-
- latestState = ParseFailure
- }
-
- if int64(currentDiff.Len()) > c.maxDiffSize {
- ctx.Logger().V(2).Info(fmt.Sprintf(
- "Diff for %s exceeded MaxDiffSize(%d)", currentDiff.PathB, c.maxDiffSize,
- ))
- break
- }
- }
- cleanupParse(ctx, currentCommit, currentDiff, diffChan, &totalLogSize)
-
- ctx.Logger().V(2).Info("finished parsing git log.", "total_log_size", totalLogSize)
-}
-
-func isMergeLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || latestState != CommitLine {
- return false
- }
- if len(line) > 6 && bytes.Equal(line[:6], []byte("Merge:")) {
- return true
- }
- return false
-}
-
-// commit 7a95bbf0199e280a0e42dbb1d1a3f56cdd0f6e05
-func isCommitLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || !(latestState == Initial ||
- latestState == MessageStartLine ||
- latestState == MessageEndLine ||
- latestState == ModeLine ||
- latestState == IndexLine ||
- latestState == BinaryFileLine ||
- latestState == ToFileLine ||
- latestState == HunkContentLine ||
- latestState == ParseFailure) {
- return false
- }
-
- if len(line) > 7 && bytes.Equal(line[:7], []byte("commit ")) {
- return true
- }
- return false
-}
-
-// Author: Bill Rich
-func isAuthorLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || !(latestState == CommitLine || latestState == MergeLine) {
- return false
- }
- if len(line) > 8 && bytes.Equal(line[:7], []byte("Author:")) {
- return true
- }
- return false
-}
-
-// AuthorDate: 2021-08-10T15:20:40+01:00
-func isAuthorDateLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || latestState != AuthorLine {
- return false
- }
- if len(line) > 10 && bytes.Equal(line[:11], []byte("AuthorDate:")) {
- return true
- }
- return false
-}
-
-// Commit: Bill Rich
-func isCommitterLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || latestState != AuthorDateLine {
- return false
- }
- if len(line) > 8 && bytes.Equal(line[:7], []byte("Commit:")) {
- return true
- }
- return false
-}
-
-// CommitDate: Wed Apr 17 19:59:28 2024 -0400
-func isCommitterDateLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || latestState != CommitterLine {
- return false
- }
- if len(line) > 10 && bytes.Equal(line[:11], []byte("CommitDate:")) {
- return true
- }
- return false
-}
-
-// Line directly after CommitterDate with only a newline.
-func isMessageStartLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || latestState != CommitterDateLine {
- return false
- }
- // TODO: Improve the implementation of this and isMessageEndLine
- if len(strings.TrimRight(string(line[:]), "\r\n")) == 0 {
- return true
- }
- return false
-}
-
-// Line that starts with 4 spaces
-func isMessageLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || !(latestState == MessageStartLine || latestState == MessageLine) {
- return false
- }
- if len(line) > 4 && bytes.Equal(line[:4], []byte(" ")) {
- return true
- }
- return false
-}
-
-// Line directly after MessageLine with only a newline.
-func isMessageEndLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || latestState != MessageLine {
- return false
- }
- if len(strings.TrimRight(string(line[:]), "\r\n")) == 0 {
- return true
- }
- return false
-}
-
-// `Notes:` or `Notes (context):`
-// See https://tylercipriani.com/blog/2022/11/19/git-notes-gits-coolest-most-unloved-feature/
-func isNotesStartLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || latestState != MessageEndLine {
- return false
- }
- if len(line) > 5 && bytes.Equal(line[:5], []byte("Notes")) {
- return true
- }
- return false
-}
-
-// Line after NotesStartLine that starts with 4 spaces
-func isNotesLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || !(latestState == NotesStartLine || latestState == NotesLine) {
- return false
- }
- if len(line) > 4 && bytes.Equal(line[:4], []byte(" ")) {
- return true
- }
- return false
-}
-
-// Line directly after NotesLine with only a newline.
-func isNotesEndLine(isStaged bool, latestState ParseState, line []byte) bool {
- if isStaged || latestState != NotesLine {
- return false
- }
- if len(strings.TrimRight(string(line[:]), "\r\n")) == 0 {
- return true
- }
- return false
-}
-
-// diff --git a/internal/addrs/move_endpoint_module.go b/internal/addrs/move_endpoint_module.go
-func isDiffLine(isStaged bool, latestState ParseState, line []byte) bool {
- if !(latestState == MessageStartLine || // Empty commit messages can go from MessageStart->Diff
- latestState == MessageEndLine ||
- latestState == NotesEndLine ||
- latestState == BinaryFileLine ||
- latestState == ModeLine ||
- latestState == IndexLine ||
- latestState == HunkContentLine ||
- latestState == ParseFailure) {
- if !(isStaged && latestState == Initial) {
- return false
- }
- }
- if len(line) > 11 && bytes.Equal(line[:11], []byte("diff --git ")) {
- return true
- }
- return false
-}
-
-// old mode 100644
-// new mode 100755
-// new file mode 100644
-// similarity index 100%
-// rename from old.txt
-// rename to new.txt
-// deleted file mode 100644
-func isModeLine(latestState ParseState, line []byte) bool {
- if !(latestState == DiffLine || latestState == ModeLine) {
- return false
- }
- // This could probably be better written.
- if (len(line) > 17 && bytes.Equal(line[:17], []byte("deleted file mode"))) ||
- (len(line) > 16 && bytes.Equal(line[:16], []byte("similarity index"))) ||
- (len(line) > 13 && bytes.Equal(line[:13], []byte("new file mode"))) ||
- (len(line) > 11 && bytes.Equal(line[:11], []byte("rename from"))) ||
- (len(line) > 9 && bytes.Equal(line[:9], []byte("rename to"))) ||
- (len(line) > 8 && bytes.Equal(line[:8], []byte("old mode"))) ||
- (len(line) > 8 && bytes.Equal(line[:8], []byte("new mode"))) {
- return true
- }
- return false
-}
-
-// index 1ed6fbee1..aea1e643a 100644
-// index 00000000..e69de29b
-func isIndexLine(latestState ParseState, line []byte) bool {
- if !(latestState == DiffLine || latestState == ModeLine) {
- return false
- }
- if len(line) > 6 && bytes.Equal(line[:6], []byte("index ")) {
- return true
- }
- return false
-}
-
-// Binary files /dev/null and b/plugin.sig differ
-func isBinaryLine(latestState ParseState, line []byte) bool {
- if latestState != IndexLine {
- return false
- }
- if len(line) > 7 && bytes.Equal(line[:6], []byte("Binary")) {
- return true
- }
- return false
-}
-
-// Get the b/ file path. Ignoring the edge case of files having `and /b` in the name for simplicity.
-func pathFromBinaryLine(line []byte) (string, bool) {
- if bytes.Contains(line, []byte("and /dev/null")) {
- return "", true
- }
-
- var (
- path string
- err error
- )
- if _, after, ok := bytes.Cut(line, []byte(" and b/")); ok {
- // drop the " differ\n"
- path = string(after[:len(after)-8])
- } else if _, after, ok = bytes.Cut(line, []byte(` and "b/`)); ok {
- // Edge case where the path is quoted.
- // https://github.com/trufflesecurity/trufflehog/issues/2384
-
- // Drop the `" differ\n` and handle escaped characters in the path.
- // e.g., "\342\200\224" instead of "—".
- // See https://github.com/trufflesecurity/trufflehog/issues/2418
- path, err = strconv.Unquote(`"` + string(after[:len(after)-9]) + `"`)
- if err != nil {
- return "", false
- }
- } else {
- // Unknown format.
- return "", false
- }
-
- return path, true
-}
-
-// --- a/internal/addrs/move_endpoint_module.go
-// --- /dev/null
-func isFromFileLine(latestState ParseState, line []byte) bool {
- if !(latestState == IndexLine || latestState == ModeLine) {
- return false
- }
- if len(line) >= 6 && bytes.Equal(line[:4], []byte("--- ")) {
- return true
- }
- return false
-}
-
-// +++ b/internal/addrs/move_endpoint_module.go
-func isToFileLine(latestState ParseState, line []byte) bool {
- if latestState != FromFileLine {
- return false
- }
- if len(line) >= 6 && bytes.Equal(line[:4], []byte("+++ ")) {
- return true
- }
- return false
-}
-
-// Get the b/ file path.
-func pathFromToFileLine(line []byte) (string, bool) {
- // Normalize paths, as they can end in `\n`, `\t\n`, etc.
- // See https://github.com/trufflesecurity/trufflehog/issues/1060
- line = bytes.TrimSpace(line)
-
- // File was deleted.
- if bytes.Equal(line, []byte("+++ /dev/null")) {
- return "", true
- }
-
- var (
- path string
- err error
- )
- if _, after, ok := bytes.Cut(line, []byte("+++ b/")); ok {
- path = string(after)
- } else if _, after, ok = bytes.Cut(line, []byte(`+++ "b/`)); ok {
- // Edge case where the path is quoted.
- // e.g., `+++ "b/C++/1 \320\243\321\200\320\276\320\272/B.c"`
-
- // Drop the trailing `"` and handle escaped characters in the path
- // e.g., "\342\200\224" instead of "—".
- // See https://github.com/trufflesecurity/trufflehog/issues/2418
- path, err = strconv.Unquote(`"` + string(after[:len(after)-1]) + `"`)
- if err != nil {
- return "", false
- }
- } else {
- // Unknown format.
- return "", false
- }
-
- return path, true
-}
-
-// @@ -298 +298 @@ func maxRetryErrorHandler(resp *http.Response, err error, numTries int)
-func isHunkLineNumberLine(latestState ParseState, line []byte) bool {
- if !(latestState == ToFileLine || latestState == HunkContentLine) {
- return false
- }
- if len(line) >= 8 && bytes.Equal(line[:2], []byte("@@")) {
- return true
- }
- return false
-}
-
-// fmt.Println("ok")
-// (There's a space before `fmt` that gets removed by the formatter.)
-func isHunkContextLine(latestState ParseState, line []byte) bool {
- if !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {
- return false
- }
- if len(line) >= 1 && bytes.Equal(line[:1], []byte(" ")) {
- return true
- }
- return false
-}
-
-// +fmt.Println("ok")
-func isHunkPlusLine(latestState ParseState, line []byte) bool {
- if !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {
- return false
- }
- if len(line) >= 1 && bytes.Equal(line[:1], []byte("+")) {
- return true
- }
- return false
-}
-
-// -fmt.Println("ok")
-func isHunkMinusLine(latestState ParseState, line []byte) bool {
- if !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {
- return false
- }
- if len(line) >= 1 && bytes.Equal(line[:1], []byte("-")) {
- return true
- }
- return false
-}
-
-// \ No newline at end of file
-func isHunkNewlineWarningLine(latestState ParseState, line []byte) bool {
- if latestState != HunkContentLine {
- return false
- }
- if len(line) >= 27 && bytes.Equal(line[:27], []byte("\\ No newline at end of file")) {
- return true
- }
- return false
-}
-
-// Newline after hunk, or an empty line, e.g.
-// +}
-//
-// commit 00920984e3435057f09cee5468850f7546dfa637 (tag: v3.42.0)
-func isHunkEmptyLine(latestState ParseState, line []byte) bool {
- if !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {
- return false
- }
- // TODO: Can this also be `\n\r`?
- if len(line) == 1 && bytes.Equal(line[:1], []byte("\n")) {
- return true
- }
- return false
-}
-
-func isCommitSeparatorLine(latestState ParseState, line []byte) bool {
- if (latestState == ModeLine || latestState == IndexLine || latestState == BinaryFileLine || latestState == ToFileLine) &&
- len(line) == 1 && bytes.Equal(line[:1], []byte("\n")) {
- return true
- }
- return false
-}
-
-func cleanupParse(ctx context.Context, currentCommit *Commit, currentDiff *Diff, diffChan chan *Diff, totalLogSize *int) {
- if err := currentDiff.finalize(); err != nil {
- ctx.Logger().Error(err, "failed to finalize diff")
- return
- }
-
- // Ignore empty or binary diffs (this condition may be redundant).
- if currentDiff != nil && (currentDiff.Len() > 0 || currentDiff.IsBinary) {
- currentDiff.Commit = currentCommit
- diffChan <- currentDiff
- }
- if currentCommit != nil {
- if totalLogSize != nil {
- *totalLogSize += currentCommit.Size
- }
- }
-}
diff --git a/pkg/gitparse/gitparse_test.go b/pkg/gitparse/gitparse_test.go
deleted file mode 100644
index e1a966aa27c9..000000000000
--- a/pkg/gitparse/gitparse_test.go
+++ /dev/null
@@ -1,2539 +0,0 @@
-package gitparse
-
-import (
- "bytes"
- "strings"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/trufflesecurity/trufflehog/v3/pkg/context"
- "github.com/trufflesecurity/trufflehog/v3/pkg/process"
- bufferwriter "github.com/trufflesecurity/trufflehog/v3/pkg/writers/buffer_writer"
- bufferedfilewriter "github.com/trufflesecurity/trufflehog/v3/pkg/writers/buffered_file_writer"
-)
-
-type testCaseLine struct {
- latestState ParseState
- line []byte
-}
-
-func TestLineChecksWithStaged(t *testing.T) {
- type testCase struct {
- passes []testCaseLine
- fails []testCaseLine
- function func(bool, ParseState, []byte) bool
- }
-
- tests := map[string]testCase{
- "commitLine": {
- passes: []testCaseLine{
- {
- Initial,
- []byte("commit 15c6105be1a18eeed1247478340dca69d02196ed"),
- },
- {
- ModeLine,
- []byte("commit 7bd16429f1f708746dabf970e54b05d2b4734997 (HEAD -> master)\n"),
- },
- {
- IndexLine,
- []byte("commit 9d60549cea17c830df3f99398993e8f6fd154468"),
- },
- {
- MessageStartLine,
- []byte("commit 4727ffb7ad6dc5130bf4b4dd166e00705abdd018"),
- },
- {
- MessageEndLine,
- []byte("commit 2a057632d7f5fa3d1c77b9aa037263211c0e0290"),
- },
- {
- HunkContentLine,
- []byte("commit b38857edb46bd0e2c86db53615ff469aa7b7966b (HEAD -> feat/git-diff-parse, origin/main, origin/HEAD, main)"),
- },
- {
- BinaryFileLine,
- []byte("commit fb76eaf17b2b923bcc3e59314cf3605bce9a8bcd (tag: v3.40.0)"),
- },
- },
- fails: []testCaseLine{
- {
- Initial,
- []byte(`fatal: ambiguous argument 'branch_2..branch_1': unknown revision or path not in the working tree.
- Use '--' to separate paths from revisions, like this:
- 'git [...] -- [...]'`),
- },
- {
- CommitLine,
- []byte("commit 15c6105be1a18eeed1247478340dca69d02196ed"),
- },
- },
- function: isCommitLine,
- },
- "mergeLine": {
- passes: []testCaseLine{
- {
- CommitLine,
- []byte("Merge: f21a95535a2 ed08d10bcf5"),
- },
- },
- fails: []testCaseLine{
- {
- CommitterDateLine,
- []byte(" Merge pull request #34511 from cescoffier/duplicated-context-doc"),
- },
- {
- CommitLine,
- []byte("notcorrect"),
- },
- },
- function: isMergeLine,
- },
- "authorLine": {
- passes: []testCaseLine{
- {
- CommitLine,
- []byte("Author: Zachary Rice "),
- },
- {
- CommitLine,
- []byte("Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>"),
- },
- },
- fails: []testCaseLine{
- {
- CommitLine,
- []byte("Date: Tue Jun 20 13:55:31 2023 -0500"),
- },
- {
- AuthorLine,
- []byte("Author: Bill Rich "),
- },
- },
- function: isAuthorLine,
- },
- "authorDateLine": {
- passes: []testCaseLine{
- {
- AuthorLine,
- []byte("AuthorDate: 2022-01-18T16:59:18-08:00"),
- },
- },
- fails: []testCaseLine{
- {
- AuthorDateLine,
- []byte(""),
- },
- {
- AuthorLine,
- []byte("notcorrect"),
- },
- },
- function: isAuthorDateLine,
- },
- "committerLine": {
- passes: []testCaseLine{
- {
- AuthorDateLine,
- []byte("Commit: Zachary Rice "),
- },
- {
- AuthorDateLine,
- []byte("Commit: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>"),
- },
- },
- fails: []testCaseLine{
- {
- CommitLine,
- []byte("Date: Tue Jun 20 13:55:31 2023 -0500"),
- },
- {
- AuthorLine,
- []byte("Author: Bill Rich "),
- },
- },
- function: isCommitterLine,
- },
- "committerDateLine": {
- passes: []testCaseLine{
- {
- CommitterLine,
- []byte("CommitDate: 2022-01-18T16:59:18-08:00"),
- },
- },
- fails: []testCaseLine{
- {
- CommitterDateLine,
- []byte(""),
- },
- {
- CommitterLine,
- []byte("notcorrect"),
- },
- },
- function: isCommitterDateLine,
- },
- "messageStartLine": {
- passes: []testCaseLine{
- {
- CommitterDateLine,
- []byte(""),
- },
- },
- fails: []testCaseLine{
- {
- AuthorLine,
- []byte("Date: Tue Jun 20 13:21:19 2023 -0700"),
- },
- {
- CommitterDateLine,
- []byte("notcorrect"),
- },
- },
- function: isMessageStartLine,
- },
- "messageLine": {
- passes: []testCaseLine{
- {
- MessageStartLine,
- []byte(" Initial docs and release automation (#5)"),
- },
- {
- MessageLine,
- []byte(" Bump github.com/googleapis/gax-go/v2 from 2.10.0 to 2.11.0 (#1406)"),
- },
- },
- fails: []testCaseLine{
- {
- AuthorLine,
- []byte("Date: Tue Jun 20 13:21:19 2023 -0700"),
- },
- {
- CommitterDateLine,
- []byte("notcorrect"),
- },
- },
- function: isMessageLine,
- },
- "messageEndLine": {
- passes: []testCaseLine{
- {
- MessageLine,
- []byte(""),
- },
- },
- fails: []testCaseLine{
- {
- MessageStartLine,
- []byte(" Initial commit"),
- },
- {
- MessageLine,
- []byte("notcorrect"),
- },
- },
- function: isMessageEndLine,
- },
- "notesStartLine": {
- passes: []testCaseLine{
- {
- MessageEndLine,
- []byte("Notes:"),
- },
- {
- MessageEndLine,
- []byte("Notes (review):"),
- },
- },
- fails: []testCaseLine{
- {
- MessageStartLine,
- []byte(""),
- },
- {
- MessageEndLine,
- []byte("notcorrect"),
- },
- },
- function: isNotesStartLine,
- },
- "notesLine": {
- passes: []testCaseLine{
- {
- NotesStartLine,
- []byte(" Submitted-by: Random J Developer "),
- },
- },
- fails: []testCaseLine{
- {
- MessageEndLine,
- []byte(""),
- },
- {
- MessageEndLine,
- []byte("notcorrect"),
- },
- },
- function: isNotesLine,
- },
- "notesEndLine": {
- passes: []testCaseLine{
- {
- NotesLine,
- []byte("\n"),
- },
- },
- fails: []testCaseLine{
- {
- MessageEndLine,
- []byte("\n"),
- },
- {
- NotesLine,
- []byte("notcorrect"),
- },
- },
- function: isNotesEndLine,
- },
- "diffLine": {
- passes: []testCaseLine{
- {
- MessageEndLine,
- []byte("diff --git a/pkg/sources/source_unit.go b/pkg/sources/source_unit.go"),
- },
- {
- MessageEndLine,
- []byte("diff --git a/ Lunch and Learn - HCDiag.pdf b/ Lunch and Learn - HCDiag.pdf"),
- },
- {
- NotesEndLine,
- []byte("diff --git \"a/one.txt\" \"b/one.txt\""),
- },
- {
- BinaryFileLine,
- []byte("diff --git a/pkg/decoders/utf16_test.go b/pkg/decoders/utf16_test.go"),
- },
- {
- HunkContentLine,
- []byte("diff --git a/pkg/decoders/utf8.go b/pkg/decoders/utf8.go"),
- },
- {
- ModeLine,
- []byte("diff --git a/pkg/decoders/utf8.go b/pkg/decoders/utf8.go"),
- },
- },
- fails: []testCaseLine{
- {
- CommitterDateLine,
- []byte(" Make trace error message so newlines aren't escaped (#1396)"),
- },
- {
- MessageLine,
- []byte("notcorrect"),
- },
- {
- NotesLine,
- []byte("diff --git a/pkg/decoders/utf8.go b/pkg/decoders/utf8.go"),
- },
- },
- function: isDiffLine,
- },
- }
-
- for name, test := range tests {
- for _, pass := range test.passes {
- if !test.function(false, pass.latestState, pass.line) {
- t.Errorf("%s: Parser did not recognize correct line. (%s)", name, string(pass.line))
- }
- }
- for _, fail := range test.fails {
- if test.function(false, fail.latestState, fail.line) {
- t.Errorf("%s: Parser did not recognize incorrect line. (%s)", name, string(fail.line))
- }
- }
- }
-}
-
-func TestLineChecksNoStaged(t *testing.T) {
- type testCase struct {
- passes []testCaseLine
- fails []testCaseLine
- function func(ParseState, []byte) bool
- }
-
- tests := map[string]testCase{
- "modeLine": {
- passes: []testCaseLine{
- {
- DiffLine,
- []byte("old mode 100644"),
- },
- {
- ModeLine,
- []byte("new mode 100755"),
- },
- {
- DiffLine,
- []byte("new file mode 100644"),
- },
- {
- DiffLine,
- []byte("similarity index 100%"),
- },
- {
- ModeLine,
- []byte("rename from old.txt"),
- },
- {
- ModeLine,
- []byte("rename to new.txt"),
- },
- {
- DiffLine,
- []byte("deleted file mode 100644"),
- },
- },
- fails: []testCaseLine{
- {
- MessageLine,
- []byte("diff --git a/pkg/common/recover.go b/pkg/common/recover.go"),
- },
- {
- DiffLine,
- []byte("notcorrect"),
- },
- },
- function: isModeLine,
- },
- "indexLine": {
- passes: []testCaseLine{
- {
- DiffLine,
- []byte("index 0a7a5b4..7682212 100644"),
- },
- {
- ModeLine,
- []byte("index 1ed6fbee1..aea1e643a 100644"),
- },
- {
- ModeLine,
- []byte("index 00000000..e69de29b"),
- },
- },
- fails: []testCaseLine{
- {
- MessageLine,
- []byte("diff --git a/pkg/sources/gitlab/gitlab.go b/pkg/sources/gitlab/gitlab.go"),
- },
- {
- DiffLine,
- []byte("notcorrect"),
- },
- },
- function: isIndexLine,
- },
- "binaryLine": {
- passes: []testCaseLine{
- {
- IndexLine,
- []byte("Binary files /dev/null and b/plugin.sig differ"),
- },
- {
- IndexLine,
- []byte("Binary files /dev/null and b/ Lunch and Learn - HCDiag.pdf differ"),
- },
- },
- fails: []testCaseLine{
- {
- DiffLine,
- []byte("index eb54cf4f..00000000"),
- },
- {
- IndexLine,
- []byte("notcorrect"),
- },
- },
- function: isBinaryLine,
- },
- "fromFileLine": {
- passes: []testCaseLine{
- {
- IndexLine,
- []byte("--- a/internal/addrs/move_endpoint_module.go"),
- },
- {
- 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
- {
- IndexLine,
- []byte("--- i/pkg/sources/filesystem/filesystem.go"),
- },
- {
- IndexLine,
- []byte("--- w/pkg/sources/gcs/gcs.go"),
- },
- {
- IndexLine,
- []byte("--- c/pkg/sources/git/git.go"),
- },
- {
- IndexLine,
- []byte("--- o/pkg/sources/github/github.go"),
- },
- },
- fails: []testCaseLine{
- {
- ModeLine,
- []byte("index 00000000..05370a3c"),
- },
- {
- IndexLine,
- []byte("notcorrect"),
- },
- },
- function: isFromFileLine,
- },
- "toFileLine": {
- passes: []testCaseLine{
- {
- FromFileLine,
- []byte("+++ a/internal/addrs/move_endpoint_module.go"),
- },
- {
- FromFileLine,
- []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
- {
- FromFileLine,
- []byte("+++ i/pkg/sources/filesystem/filesystem.go"),
- },
- {
- FromFileLine,
- []byte("+++ w/pkg/sources/gcs/gcs.go"),
- },
- {
- FromFileLine,
- []byte("+++ c/pkg/sources/git/git.go"),
- },
- {
- FromFileLine,
- []byte("+++ o/pkg/sources/github/github.go"),
- },
- },
- fails: []testCaseLine{
- {
- IndexLine,
- []byte("--- a/pkg/detectors/shortcut/shortcut_test.go"),
- },
- {
- FromFileLine,
- []byte("notcorrect"),
- },
- {
- HunkContentLine,
- []byte("+++ The application will interface with REDACTED and REDACTED (REDACTED team)"),
- },
- },
- function: isToFileLine,
- },
- "lineNumberLine": {
- passes: []testCaseLine{
- {
- ToFileLine,
- []byte("@@ -298 +298 @@ func maxRetryErrorHandler(resp *http.Response, err error, numTries int)"),
- },
- {
- HunkContentLine,
- []byte("@@ -121 +121 @@ require ("),
- },
- },
- fails: []testCaseLine{
- {
- FromFileLine,
- []byte("+++ b/Dockerfile"),
- },
- {
- ToFileLine,
- []byte("notcorrect"),
- },
- },
- function: isHunkLineNumberLine,
- },
- "hunkContextLine": {
- passes: []testCaseLine{
- {
- HunkLineNumberLine,
- []byte(" fmt.Println"),
- },
- {
- HunkContentLine,
- []byte(" ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)"),
- },
- },
- fails: []testCaseLine{
- {
- ToFileLine,
- []byte("@@ -176 +176 @@ require ("),
- },
- {
- HunkLineNumberLine,
- []byte("+import ("),
- },
- },
- function: isHunkContextLine,
- },
- "hunkPlusLine": {
- passes: []testCaseLine{
- {
- HunkLineNumberLine,
- []byte("+ github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect"),
- },
- {
- HunkContentLine,
- []byte("+cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0="),
- },
- },
- fails: []testCaseLine{
- {
- ToFileLine,
- []byte("@@ -176 +176 @@ require ("),
- },
- {
- HunkLineNumberLine,
- []byte("-import ("),
- },
- {
- HunkLineNumberLine,
- []byte("notcorrect"),
- },
- },
- function: isHunkPlusLine,
- },
- "hunkMinusLine": {
- passes: []testCaseLine{
- {
- HunkLineNumberLine,
- []byte("-fmt.Println"),
- },
- {
- HunkContentLine,
- []byte(`- return []string{"sql", "database", "Data Source"}`),
- },
- },
- fails: []testCaseLine{
- {
- ToFileLine,
- []byte("@@ -176 +176 @@ require ("),
- },
- {
- HunkLineNumberLine,
- []byte("+import ("),
- },
- {
- HunkLineNumberLine,
- []byte("notcorrect"),
- },
- },
- function: isHunkMinusLine,
- },
- "hunkNewlineWarningLine": {
- passes: []testCaseLine{
- {
- HunkContentLine,
- []byte("\\ No newline at end of file"),
- },
- },
- fails: []testCaseLine{
- {
- ToFileLine,
- []byte("@@ -176 +176 @@ require ("),
- },
- {
- HunkContentLine,
- []byte(" \\ No newline at end of file is the current error"),
- },
- },
- function: isHunkNewlineWarningLine,
- },
- "hunkEmptyLine": {
- passes: []testCaseLine{
- {
- HunkContentLine,
- []byte("\n"),
- },
- },
- fails: []testCaseLine{
- {
- HunkLineNumberLine,
- []byte(` return "", errors.WrapPrefix(err, "repo remote cannot be sanitized as URI", 0)`),
- },
- {
- HunkContentLine,
- []byte(" \n"),
- },
- },
- function: isHunkEmptyLine,
- },
- }
-
- for name, test := range tests {
- for _, pass := range test.passes {
- if !test.function(pass.latestState, pass.line) {
- t.Errorf("%s: Parser did not recognize correct line. (%s)", name, string(pass.line))
- }
- }
- for _, fail := range test.fails {
- if test.function(fail.latestState, fail.line) {
- t.Errorf("%s: Parser did not recognize incorrect line. (%s)", name, string(fail.line))
- }
- }
- }
-}
-
-func TestBinaryPathParse(t *testing.T) {
- cases := map[string]string{
- "Binary files a/trufflehog_3.42.0_linux_arm64.tar.gz and /dev/null differ\n": "",
- "Binary files /dev/null and b/plugin.sig differ\n": "plugin.sig",
- "Binary files /dev/null and b/ Lunch and Learn - HCDiag.pdf differ\n": " Lunch and Learn - HCDiag.pdf",
- "Binary files /dev/null and \"b/assets/retailers/ON-ikony-Platforma-ecom \\342\\200\\224 kopia.png\" differ\n": "assets/retailers/ON-ikony-Platforma-ecom — kopia.png",
- "Binary files /dev/null and \"b/\\346\\267\\261\\345\\272\\246\\345\\255\\246\\344\\271\\240500\\351\\227\\256-Tan-00\\347\\233\\256\\345\\275\\225.docx\" differ\n": "深度学习500问-Tan-00目录.docx",
- }
-
- for name, expected := range cases {
- filename, ok := pathFromBinaryLine([]byte(name))
- if !ok {
- t.Errorf("Failed to get path: %s", name)
- }
- if filename != expected {
- t.Errorf("Expected: %s, Got: %s", expected, filename)
- }
- }
-}
-
-func TestToFileLinePathParse(t *testing.T) {
- cases := map[string]string{
- "+++ /dev/null\n": "",
- "+++ b/embeds.xml\t\n": "embeds.xml",
- "+++ \"b/C++/1 \\320\\243\\321\\200\\320\\276\\320\\272/B.c\"\t\n": "C++/1 Урок/B.c",
- }
-
- for name, expected := range cases {
- filename, ok := pathFromToFileLine([]byte(name))
- if !ok {
- t.Errorf("Failed to get path: %s", name)
- }
- if filename != expected {
- t.Errorf("Expected: %s, Got: %s", expected, filename)
- }
- }
-}
-
-// `asserts` on `Diff`'s _structure_, giving better test output than just comparing two
-// diffs.
-func assertDiffEqualToExpected(t *testing.T, expected *Diff, actual *Diff) {
-
- // Use `cmp.Diff` to automatically compare all the exported fields. This allows this test to grow automatically if
- // new exported fields are added to these structs. However, the most important field we want to test is unexpected
- // (i.e. contentWriter) which is where the actual content of the diff is stored. We break this out next.
- opts := []cmp.Option{
- cmpopts.IgnoreUnexported(Diff{}, Commit{}, strings.Builder{}),
- cmpopts.IgnoreFields(Commit{}, "Size"),
- }
- if diff := cmp.Diff(expected, actual, opts...); diff != "" {
- t.Errorf("%s", diff)
- }
-
- // Here's where we compare the actual content of the diff. We break that out and test it separately so that we can
- // keep this test relatively easy to understand and still get meaningful test output on the diff itself
-
- // If the test author hasn't specified a contentWriter, then we don't want to explode, but we _do_
- // want to confirm that the actual diff _also_ is nil there
- if expected.contentWriter == nil {
- assert.Nil(t, actual.contentWriter)
- }
- // Check that the content of the diff itself is as expected for non-binary diffs
- if expected.contentWriter != nil && !actual.IsBinary {
- assert.Equal(t, expected.contentWriter.Len(), actual.contentWriter.Len())
- expectedDiffStr, err := expected.contentWriter.String()
- require.NoError(t, err)
- actualDiffStr, err := actual.contentWriter.String()
- assert.NoError(t, err)
- assert.Equal(t, expectedDiffStr, actualDiffStr)
- }
-
- // TODO - Add test coverage for binary diffs (if it isn't already elsewhere)
-}
-
-func TestCommitParsing(t *testing.T) {
- // Feels bad to skip tests forever and then just forget about them. Skip for a while.
- if time.Now().Before(time.Date(2025, time.July, 1, 0, 0, 0, 0, time.UTC)) {
- t.Skip("This is failing intermittently. Skipping for now")
- }
- expected := expectedDiffs()
-
- beforeProcesses := process.GetGitProcessList()
-
- r := bytes.NewReader([]byte(commitLog))
- diffChan := make(chan *Diff)
- parser := NewParser()
- go func() {
- parser.FromReader(context.Background(), r, diffChan, false)
- }()
- i := 0
- for diff := range diffChan {
- if len(expected) <= i {
- t.Errorf("Missing expected case for commit: %+v", diff)
- break
- }
-
- assertDiffEqualToExpected(t, expected[i], diff)
- i++
- }
-
- afterProcesses := process.GetGitProcessList()
- zombies := process.DetectGitZombies(beforeProcesses, afterProcesses)
-
- if len(zombies) > 0 {
- t.Errorf("Detected %d zombie git processes: %v", len(zombies), zombies)
- }
-}
-
-func newBufferedFileWriterWithContent(content []byte) *bufferedfilewriter.BufferedFileWriter {
- b := bufferedfilewriter.New()
- _, err := b.Write(content) // Using Write method to add content
- if err != nil {
- panic(err)
- }
- return b
-}
-
-func newBufferWithContent(content []byte) *bufferwriter.BufferWriter {
- b := bufferwriter.New()
- _, _ = b.Write(content) // Using Write method to add content
- return b
-}
-
-func TestStagedDiffParsing(t *testing.T) {
- expected := []*Diff{
- {
- PathB: "aws",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("[default]\naws_access_key_id = AKIAXYZDQCEN4B6JSJQI\naws_secret_access_key = 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\noutput = json\nregion = us-east-2\n")),
- IsBinary: false,
- },
- {
- PathB: "aws2",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("\n\nthis is the secret: [Default]\nAccess key Id: AKIAILE3JG6KMS3HZGCA\nSecret Access Key: 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\n\nokay thank you bye\n")),
- IsBinary: false,
- },
- {
- PathB: "core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java",
- LineStart: 3,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("/**\n * This is usually used for command mode applications with a startup logic. The logic is executed inside\n * {@link QuarkusApplication#run} method before the main application exits.\n */\n")),
- IsBinary: false,
- },
- {
- PathB: "trufflehog_3.42.0_linux_arm64.tar.gz",
- IsBinary: true,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent(nil),
- },
- {
- PathB: "tzu",
- LineStart: 11,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("\n\n\n\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\n")),
- IsBinary: false,
- },
- {
- PathB: "lao",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("The Way that can be told of is not the eternal Way;\nThe name that can be named is not the eternal name.\nThe Nameless is the origin of Heaven and Earth;\nThe Named is the mother of all things.\nTherefore let there always be non-being,\n so we may see their subtlety,\nAnd let there always be being,\n so we may see their outcome.\nThe two are the same,\nBut after they are produced,\n they have different names.\n")),
- IsBinary: false,
- },
- {
- PathB: "tzu",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("The Nameless is the origin of Heaven and Earth;\nThe named is the mother of all things.\n\nTherefore let there always be non-being,\n so we may see their subtlety,\nAnd let there always be being,\n so we may see their outcome.\nThe two are the same,\nBut after they are produced,\n they have different names.\nThey both may be called deep and profound.\nDeeper and more profound,\nThe door of all subtleties!\n")),
- IsBinary: false,
- },
- }
-
- r := bytes.NewReader([]byte(stagedDiffs))
- diffChan := make(chan *Diff)
- parser := NewParser()
- go func() {
- parser.FromReader(context.Background(), r, diffChan, true)
- }()
- i := 0
- for diff := range diffChan {
- if len(expected) <= i {
- t.Errorf("Missing expected case for commit: %+v", diff)
- break
- }
-
- assertDiffEqualToExpected(t, expected[i], diff)
- i++
- }
-}
-
-func TestStagedDiffParsingBufferedFileWriter(t *testing.T) {
- expected := []*Diff{
- {
- PathB: "aws",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferedFileWriterWithContent([]byte("[default]\naws_access_key_id = AKIAXYZDQCEN4B6JSJQI\naws_secret_access_key = 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\noutput = json\nregion = us-east-2\n")),
- IsBinary: false,
- },
- {
- PathB: "aws2",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferedFileWriterWithContent([]byte("\n\nthis is the secret: [Default]\nAccess key Id: AKIAILE3JG6KMS3HZGCA\nSecret Access Key: 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\n\nokay thank you bye\n")),
- IsBinary: false,
- },
- {
- PathB: "core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java",
- LineStart: 3,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferedFileWriterWithContent([]byte("/**\n * This is usually used for command mode applications with a startup logic. The logic is executed inside\n * {@link QuarkusApplication#run} method before the main application exits.\n */\n")),
- IsBinary: false,
- },
- {
- PathB: "trufflehog_3.42.0_linux_arm64.tar.gz",
- IsBinary: true,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferedFileWriterWithContent(nil),
- },
- {
- PathB: "tzu",
- LineStart: 11,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferedFileWriterWithContent([]byte("\n\n\n\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\n")),
- IsBinary: false,
- },
- {
- PathB: "lao",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferedFileWriterWithContent([]byte("The Way that can be told of is not the eternal Way;\nThe name that can be named is not the eternal name.\nThe Nameless is the origin of Heaven and Earth;\nThe Named is the mother of all things.\nTherefore let there always be non-being,\n so we may see their subtlety,\nAnd let there always be being,\n so we may see their outcome.\nThe two are the same,\nBut after they are produced,\n they have different names.\n")),
- IsBinary: false,
- },
- {
- PathB: "tzu",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferedFileWriterWithContent([]byte("The Nameless is the origin of Heaven and Earth;\nThe named is the mother of all things.\n\nTherefore let there always be non-being,\n so we may see their subtlety,\nAnd let there always be being,\n so we may see their outcome.\nThe two are the same,\nBut after they are produced,\n they have different names.\nThey both may be called deep and profound.\nDeeper and more profound,\nThe door of all subtleties!\n")),
- IsBinary: false,
- },
- }
-
- r := bytes.NewReader([]byte(stagedDiffs))
- diffChan := make(chan *Diff)
- parser := NewParser()
- go func() {
- parser.FromReader(context.Background(), r, diffChan, true)
- }()
- i := 0
- for diff := range diffChan {
- if len(expected) <= i {
- t.Errorf("Missing expected case for commit: %+v", diff)
- break
- }
-
- assertDiffEqualToExpected(t, expected[i], diff)
- i++
- }
-}
-
-func TestCommitParseFailureRecovery(t *testing.T) {
- expected := []*Diff{
- {
- PathB: ".travis.yml",
- LineStart: 1,
- Commit: &Commit{
- Hash: "df393b4125c2aa217211b2429b8963d0cefcee27",
- Author: "Stephen ",
- Committer: "Stephen ",
- Date: newTime("2017-12-06T14:44:41-08:00"),
- Message: newStringBuilderValue("Add travis testing\n"),
- },
- contentWriter: newBufferWithContent([]byte("language: python\npython:\n - \"2.6\"\n - \"2.7\"\n - \"3.2\"\n - \"3.3\"\n - \"3.4\"\n - \"3.5\"\n - \"3.5-dev\" # 3.5 development branch\n - \"3.6\"\n - \"3.6-dev\" # 3.6 development branch\n - \"3.7-dev\" # 3.7 development branch\n - \"nightly\"\n")),
- IsBinary: false,
- },
- {
- Commit: &Commit{
- Hash: "3d76a97faad96e0f326afb61c232b9c2a18dca35",
- Author: "John Smith ",
- Committer: "John Smith ",
- Date: newTime("2023-07-11T18:03:54-04:00"),
- Message: strings.Builder{},
- },
- },
- {
- PathB: "tzu",
- LineStart: 11,
- Commit: &Commit{
- Hash: "7bd16429f1f708746dabf970e54b05d2b4734997",
- Author: "John Smith ",
- Committer: "John Smith ",
- Date: newTime("2023-07-11T18:10:49-04:00"),
- Message: newStringBuilderValue("Change file\n"),
- },
- contentWriter: newBufferWithContent([]byte("\n\n\n\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\n")),
- IsBinary: false,
- },
- }
-
- r := bytes.NewReader([]byte(recoverableCommits))
- diffChan := make(chan *Diff)
- parser := NewParser()
- go func() {
- parser.FromReader(context.Background(), r, diffChan, false)
- }()
- i := 0
- for diff := range diffChan {
- assertDiffEqualToExpected(t, expected[i], diff)
- i++
- }
-}
-
-func TestCommitParseFailureRecoveryBufferedFileWriter(t *testing.T) {
- expected := []*Diff{
- {
- PathB: ".travis.yml",
- LineStart: 1,
- Commit: &Commit{
- Hash: "df393b4125c2aa217211b2429b8963d0cefcee27",
- Author: "Stephen ",
- Committer: "Stephen ",
- Date: newTime("2017-12-06T14:44:41-08:00"),
- Message: newStringBuilderValue("Add travis testing\n"),
- },
- contentWriter: newBufferedFileWriterWithContent([]byte("language: python\npython:\n - \"2.6\"\n - \"2.7\"\n - \"3.2\"\n - \"3.3\"\n - \"3.4\"\n - \"3.5\"\n - \"3.5-dev\" # 3.5 development branch\n - \"3.6\"\n - \"3.6-dev\" # 3.6 development branch\n - \"3.7-dev\" # 3.7 development branch\n - \"nightly\"\n")),
- IsBinary: false,
- },
- {
- Commit: &Commit{
- Hash: "3d76a97faad96e0f326afb61c232b9c2a18dca35",
- Author: "John Smith ",
- Committer: "John Smith ",
- Date: newTime("2023-07-11T18:03:54-04:00"),
- Message: strings.Builder{},
- },
- },
- {
- PathB: "tzu",
- LineStart: 11,
- Commit: &Commit{
- Hash: "7bd16429f1f708746dabf970e54b05d2b4734997",
- Author: "John Smith ",
- Committer: "John Smith ",
- Date: newTime("2023-07-11T18:10:49-04:00"),
- Message: newStringBuilderValue("Change file\n"),
- },
- contentWriter: newBufferedFileWriterWithContent([]byte("\n\n\n\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\n")),
- IsBinary: false,
- },
- }
-
- r := bytes.NewReader([]byte(recoverableCommits))
- diffChan := make(chan *Diff)
- parser := NewParser()
- go func() {
- parser.FromReader(context.Background(), r, diffChan, false)
- }()
- i := 0
- for diff := range diffChan {
- if len(expected) <= i {
- t.Errorf("Missing expected case for commit: %+v", diff)
- break
- }
-
- assertDiffEqualToExpected(t, expected[i], diff)
- i++
- }
-}
-
-const recoverableCommits = `commit df393b4125c2aa217211b2429b8963d0cefcee27
-Author: Stephen
-AuthorDate: 2017-12-06T14:44:41-08:00
-Commit: Stephen
-CommitDate: 2017-12-06T14:44:41-08:00
-
- Add travis testing
-
-diff --git a/.gitignore b/.gitignore
-index ede6aa39..bb85dcc3 100644
---- a/.gitignore
-+++ b/.gitignore
->>>>ERRANT LINE<<<<
- /build/
- /dist/
- /truffleHog.egg-info/
--*/__pycache__/
-+**/__pycache__/
-+**/*.pyc
-diff --git a/.travis.yml b/.travis.yml
-new file mode 100644
-index 00000000..33b6f107
---- /dev/null
-+++ b/.travis.yml
-@@ -0,0 +1,13 @@
-+language: python
-+python:
-+ - "2.6"
-+ - "2.7"
-+ - "3.2"
-+ - "3.3"
-+ - "3.4"
-+ - "3.5"
-+ - "3.5-dev" # 3.5 development branch
-+ - "3.6"
-+ - "3.6-dev" # 3.6 development branch
-+ - "3.7-dev" # 3.7 development branch
-+ - "nightly"
-diff --git a/requirements.txt b/requirements.txt
-new file mode 100644
-index 00000000..e69de29b
-
-commit 3d76a97faad96e0f326afb61c232b9c2a18dca35 (HEAD -> master)
-Author: John Smith
-AuthorDate: 2023-07-11T18:03:54-04:00
-Commit: John Smith
-CommitDate: 2023-07-11T18:03:54-04:00
-
-diff --git a/sample.txt b/sample.txt
-new file mode 100644
-index 0000000..af5626b
---- /dev/null
-+++ b/sample.txt
-@@ -0,0 +1 @@
-!!!ERROR!!!
-
-commit 7bd16429f1f708746dabf970e54b05d2b4734997 (HEAD -> master)
-Author: John Smith
-AuthorDate: 2023-07-11T18:10:49-04:00
-Commit: John Smith
-CommitDate: 2023-07-11T18:10:49-04:00
-
- Change file
-
-diff --git a/tzu b/tzu
-index 5af88a8..c729cdb 100644
---- a/tzu
-+++ b/tzu
-@@ -11,3 +11,5 @@ But after they are produced,
- They both may be called deep and profound.
- Deeper and more profound,
- The door of all subtleties!
-+
-+Source: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format
-`
-
-func TestDiffParseFailureRecovery(t *testing.T) {
- expected := []*Diff{
- {
- PathB: "aws",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("[default]\naws_access_key_id = AKIAXYZDQCEN4B6JSJQI\naws_secret_access_key = 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\noutput = json\nregion = us-east-2\n")),
- IsBinary: false,
- },
- {
- PathB: "tzu",
- LineStart: 11,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("\n\n\n\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\n")),
- IsBinary: false,
- },
- {
- PathB: "tzu",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("The Nameless is the origin of Heaven and Earth;\nThe named is the mother of all things.\n\nTherefore let there always be non-being,\n so we may see their subtlety,\nAnd let there always be being,\n so we may see their outcome.\nThe two are the same,\nBut after they are produced,\n they have different names.\nThey both may be called deep and profound.\nDeeper and more profound,\nThe door of all subtleties!\n")),
- IsBinary: false,
- },
- }
-
- r := bytes.NewReader([]byte(recoverableDiffs))
- diffChan := make(chan *Diff)
- parser := NewParser()
- go func() {
- parser.FromReader(context.Background(), r, diffChan, true)
- }()
- i := 0
- for diff := range diffChan {
- if len(expected) <= i {
- t.Errorf("Missing expected case for commit: %+v", diff)
- break
- }
-
- assertDiffEqualToExpected(t, expected[i], diff)
- i++
- }
-}
-
-func TestDiffParseFailureRecoveryBufferedFileWriter(t *testing.T) {
- expected := []*Diff{
- {
- PathB: "aws",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("[default]\naws_access_key_id = AKIAXYZDQCEN4B6JSJQI\naws_secret_access_key = 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\noutput = json\nregion = us-east-2\n")),
- IsBinary: false,
- },
- {
- PathB: "tzu",
- LineStart: 11,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("\n\n\n\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\n")),
- IsBinary: false,
- },
- {
- PathB: "tzu",
- LineStart: 1,
- Commit: &Commit{
- Hash: "",
- Author: "",
- Date: newTime("0001-01-01 00:00:00 +0000 UTC"),
- Message: strings.Builder{},
- },
- contentWriter: newBufferWithContent([]byte("The Nameless is the origin of Heaven and Earth;\nThe named is the mother of all things.\n\nTherefore let there always be non-being,\n so we may see their subtlety,\nAnd let there always be being,\n so we may see their outcome.\nThe two are the same,\nBut after they are produced,\n they have different names.\nThey both may be called deep and profound.\nDeeper and more profound,\nThe door of all subtleties!\n")),
- IsBinary: false,
- },
- }
-
- r := bytes.NewReader([]byte(recoverableDiffs))
- diffChan := make(chan *Diff)
- parser := NewParser()
- go func() {
- parser.FromReader(context.Background(), r, diffChan, true)
- }()
- i := 0
- for diff := range diffChan {
- if len(expected) <= i {
- t.Errorf("Missing expected case for commit: %+v", diff)
- break
- }
-
- assertDiffEqualToExpected(t, expected[i], diff)
- i++
- }
-}
-
-const recoverableDiffs = `diff --git a/aws b/aws
-index 2ee133b..12b4843 100644
---- a/aws
-+++ b/aws
-@@ -1,7 +1,5 @@
--blah blaj
--
--this is the secret: [Default]
--Access key Id: AKIAILE3JG6KMS3HZGCA
--Secret Access Key: 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
--
--okay thank you bye
-+[default]
-+aws_access_key_id = AKIAXYZDQCEN4B6JSJQI
-+aws_secret_access_key = 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
-+output = json
-+region = us-east-2
-
-diff --git a/aws2 b/aws2
-index 239b415..2ee133b 100644
---- a/aws2
-+++ b/aws2
-!!!ERROR!!!
- blah blaj
-
--this is the secret: AKIA2E0A8F3B244C9986
-+this is the secret: [Default]
-+Access key Id: AKIAILE3JG6KMS3HZGCA
-+Secret Access Key: 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
-
--okay thank you bye
-\ No newline at end of file
-+okay thank you bye
-
-diff --git c/requirements.txt i/requirements.txt
-new file mode 100644
-index 00000000..e69de29b
-
-diff --git a/tzu b/tzu
-index 5af88a8..c729cdb 100644
---- a/tzu
-+++ b/tzu
-@@ -11,3 +11,5 @@ But after they are produced,
- They both may be called deep and profound.
- Deeper and more profound,
- The door of all subtleties!
-+
-+Source: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format
-
-diff --git a/lao b/lao
-new file mode 100644
-!!!ERROR!!!!
---- /dev/null
-+++ b/lao
-@@ -0,0 +1,11 @@
-+The Way that can be told of is not the eternal Way;
-+The name that can be named is not the eternal name.
-+The Nameless is the origin of Heaven and Earth;
-+The Named is the mother of all things.
-+Therefore let there always be non-being,
-+ so we may see their subtlety,
-+And let there always be being,
-+ so we may see their outcome.
-+The two are the same,
-+But after they are produced,
-+ they have different names.
-diff --git a/tzu b/tzu
-new file mode 100644
-index 0000000..5af88a8
---- /dev/null
-+++ b/tzu
-@@ -0,0 +1,13 @@
-+The Nameless is the origin of Heaven and Earth;
-+The named is the mother of all things.
-+
-+Therefore let there always be non-being,
-+ so we may see their subtlety,
-+And let there always be being,
-+ so we may see their outcome.
-+The two are the same,
-+But after they are produced,
-+ they have different names.
-+They both may be called deep and profound.
-+Deeper and more profound,
-+The door of all subtleties!
-`
-
-func TestMaxDiffSize(t *testing.T) {
- parser := NewParser(WithMaxDiffSize(1024 * 1024)) // Setting max diff size to 1MB for the test
- builder := strings.Builder{}
- builder.WriteString(singleCommitSingleDiff)
-
- // Generate a diff that is larger than the maxDiffSize.
- for i := int64(0); i <= parser.maxDiffSize/1024+10; i++ {
- builder.WriteString("+" + strings.Repeat("0", 1024) + "\n")
- }
- bigReader := strings.NewReader(builder.String())
-
- diffChan := make(chan *Diff, 1) // Buffer to prevent blocking
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) // Timeout to prevent long wait
- defer cancel()
-
- go func() {
- parser.FromReader(ctx, bigReader, diffChan, false)
- }()
-
- select {
- case diff := <-diffChan:
- if int64(diff.Len()) > parser.maxDiffSize+1024 {
- t.Errorf("diff did not match MaxDiffSize. Got: %d, expected (max): %d", diff.Len(), parser.maxDiffSize+1024)
- }
- case <-ctx.Done():
- t.Fatal("Test timed out")
- }
-}
-
-func TestMaxCommitSize(t *testing.T) {
- parser := NewParser(WithMaxCommitSize(1))
- commitText := bytes.Buffer{}
- commitText.WriteString(singleCommitMultiDiff)
- diffChan := make(chan *Diff)
- ctx, cancel := context.WithTimeout(context.Background(), time.Duration(1)*time.Second)
- defer cancel()
- go func() {
- parser.FromReader(ctx, &commitText, diffChan, false)
- }()
- diffCount := 0
- for range diffChan {
- diffCount++
- }
- if diffCount != 2 {
- t.Errorf("Commit count does not match. Got: %d, expected: %d", diffCount, 2)
- }
-
-}
-
-const commitLog = `commit e50b135fd29e91b2fbb25923797f5ecffe59f359
-Author: lionzxy
-AuthorDate: 2017-03-01T18:20:04+03:00
-Commit: lionzxy
-CommitDate: 2017-03-01T18:20:04+03:00
-
- Все работает, но он не принимает :(
-
-diff --git "a/C++/1 \320\243\321\200\320\276\320\272/.idea/workspace.xml" "b/C++/1 \320\243\321\200\320\276\320\272/.idea/workspace.xml"
-index 85bfb17..89b08b5 100644
---- "a/C++/1 \320\243\321\200\320\276\320\272/.idea/workspace.xml"
-+++ "b/C++/1 \320\243\321\200\320\276\320\272/.idea/workspace.xml"
-@@ -29,8 +29,8 @@
-
-
-
--
--
-+
-+
-
-
-
-
-commit fd6e99e7a80199b76a694603be57c5ade1de18e7
-Author: Jaliborc
-AuthorDate: 2011-04-25T16:28:06+01:00
-Commit: Jaliborc
-CommitDate: 2011-04-25T16:28:06+01:00
-
- Added Unusable coloring
-
-Notes:
- Message-Id: <1264640755-22447-1-git-send-email-user@example.de>
-
-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 @@
-
-
-
-+
-
-
-
-\ 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
-AuthorDate: 2023-07-11T22:26:11-04:00
-Commit: John Smith
-CommitDate: 2023-07-11T22:26:11-04:00
-
-commit c904e0f5cd9f30ae520c66bd5f70806219fe7ca2 (HEAD -> master)
-Author: John Smith
-AuthorDate: 2023-07-10T10:17:11-04:00
-Commit: John Smith
-CommitDate: 2023-07-10T10:17:11-04:00
-
- Empty Commit
-
-commit 3d76a97faad96e0f326afb61c232b9c2a18dca35 (HEAD -> master)
-Author: John Smith
-AuthorDate: 2023-07-11T18:03:54-04:00
-Commit: John Smith
-CommitDate: 2023-07-11T18:03:54-04:00
-
-diff --git a/sample.txt b/sample.txt
-new file mode 100644
-index 0000000..af5626b
---- /dev/null
-+++ b/sample.txt
-@@ -0,0 +1 @@
-+Hello, world!
-
-commit df393b4125c2aa217211b2429b8963d0cefcee27
-Author: Stephen
-AuthorDate: 2017-12-06T14:44:41-08:00
-Commit: Stephen
-CommitDate: 2017-12-06T14:44:41-08:00
-
- Add travis testing
-
-diff --git a/.gitignore b/.gitignore
-index ede6aa39..bb85dcc3 100644
---- a/.gitignore
-+++ b/.gitignore
-@@ -1,4 +1,5 @@
- /build/
- /dist/
- /truffleHog.egg-info/
--*/__pycache__/
-+**/__pycache__/
-+**/*.pyc
-diff --git a/.travis.yml b/.travis.yml
-new file mode 100644
-index 00000000..33b6f107
---- /dev/null
-+++ b/.travis.yml
-@@ -0,0 +1,13 @@
-+language: python
-+python:
-+ - "2.6"
-+ - "2.7"
-+ - "3.2"
-+ - "3.3"
-+ - "3.4"
-+ - "3.5"
-+ - "3.5-dev" # 3.5 development branch
-+ - "3.6"
-+ - "3.6-dev" # 3.6 development branch
-+ - "3.7-dev" # 3.7 development branch
-+ - "nightly"
-diff --git a/requirements.txt b/requirements.txt
-new file mode 100644
-index 00000000..e69de29b
-
-commit 4218c39d99b5f30153f62471c1be1c1596f0a4d4
-Author: Dustin Decker
-AuthorDate: 2022-01-13T12:02:24-08:00
-Commit: Dustin Decker
-CommitDate: 2022-01-13T12:02:24-08:00
-
- Initial CLI w/ partially implemented Git source and demo detector (#1)
-
-diff --git a/Dockerfile b/Dockerfile
-new file mode 100644
-index 00000000..e69de29b
-diff --git a/Makefile b/Makefile
-new file mode 100644
-index 00000000..453cf52c
---- /dev/null
-+++ b/Makefile
-@@ -0,0 +1,32 @@
-+PROTOS_IMAGE=us-docker.pkg.dev/thog-artifacts/public/go-ci-1.17-1
-+
-+.PHONY: check
-+.PHONY: test
-+.PHONY: test-race
-+.PHONY: run
-+.PHONY: install
-+.PHONY: protos
-+.PHONY: protos-windows
-+.PHONY: vendor
-+
-+install:
-+ CGO_ENABLED=0 go install .
-+
-+check:
-+ go fmt $(shell go list ./... | grep -v /vendor/)
-+ go vet $(shell go list ./... | grep -v /vendor/)
-+
-+test:
-+ CGO_ENABLED=0 go test $(shell go list ./... | grep -v /vendor/)
-+
-+test-race:
-+ CGO_ENABLED=1 go test -race $(shell go list ./... | grep -v /vendor/)
-+
-+bench:
-+ CGO_ENABLED=0 go test $(shell go list ./pkg/secrets/... | grep -v /vendor/) -benchmem -run=xxx -bench .
-+
-+run:
-+ CGO_ENABLED=0 go run . git file://.
-+
-+protos:
-+ docker run -u "$(shell id -u)" -v "$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))":/pwd "${PROTOS_IMAGE}" bash -c "cd /pwd; /pwd/scripts/gen_proto.sh"
-diff --git a/README.md b/README.md
-new file mode 100644
-index 00000000..e69de29b
-diff --git a/go.mod b/go.mod
-new file mode 100644
-index 00000000..7fb2f73c
---- /dev/null
-+++ b/go.mod
-
-commit 934cf5d255fd8e28b33f5a6ba64276caf0b284bf (HEAD -> master)
-Author: John Smith
-AuthorDate: 2023-07-11T18:43:22-04:00
-Commit: John Smith
-CommitDate: 2023-07-11T18:43:22-04:00
-
- Test toFile/plusLine parsing
-
-diff --git a/plusLine.txt b/plusLine.txt
-new file mode 100644
-index 0000000..451be67
---- /dev/null
-+++ b/plusLine.txt
-@@ -0,0 +1,3 @@
-+-- test
-+++ test
-+
-
-commit 2a5d703b02b52d65c65ee9f7928f158b919ab741
-Author: Sergey Beryozkin
-AuthorDate: 2023-07-07T17:44:26+01:00
-Commit: Sergey Beryozkin
-CommitDate: 2023-07-07T17:44:26+01:00
-
- Do not refresh OIDC session if the user is requesting logout
-
-diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutTokenCache.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutTokenCache.java
-index 096f5b4b092..4150096851c 100644
---- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutTokenCache.java
-+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutTokenCache.java
-@@ -45,6 +45,10 @@ public TokenVerificationResult removeTokenVerification(String token) {
- return entry == null ? null : entry.result;
- }
-
-+ public boolean containsTokenVerification(String token) {
-+ return cacheMap.containsKey(token);
-+ }
-+
- public void clearCache() {
- cacheMap.clear();
- size.set(0);
-diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
-index a9a9699eecd..435cefdf313 100644
---- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
-+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
-@@ -1014,7 +1023,7 @@ private String buildUri(RoutingContext context, boolean forceHttps, String autho
- .toString();
- }
-
-- private boolean isLogout(RoutingContext context, TenantConfigContext configContext) {
-+ private boolean isRpInitiatedLogout(RoutingContext context, TenantConfigContext configContext) {
- return isEqualToRequestPath(configContext.oidcConfig.logout.path, context, configContext);
- }
-
-@@ -1205,4 +1214,38 @@ static String getCookieSuffix(OidcTenantConfig oidcConfig) {
- ? (tenantIdSuffix + UNDERSCORE + oidcConfig.authentication.cookieSuffix.get())
- : tenantIdSuffix;
- }
-+
-+ private class LogoutCall implements Function> {
-+ RoutingContext context;
-+ TenantConfigContext configContext;
-+ String idToken;
-+
-+ LogoutCall(RoutingContext context, TenantConfigContext configContext, String idToken) {
-+ this.context = context;
-+ this.configContext = configContext;
-+ this.idToken = idToken;
-+ }
-+
-+ @Override
-+ public Uni apply(SecurityIdentity identity) {
-+ if (isRpInitiatedLogout(context, configContext)) {
-+ LOG.debug("Performing an RP initiated logout");
-+ fireEvent(SecurityEvent.Type.OIDC_LOGOUT_RP_INITIATED, identity);
-+ return buildLogoutRedirectUriUni(context, configContext, idToken);
-+ }
-+ if (isBackChannelLogoutPendingAndValid(configContext, identity)
-+ || isFrontChannelLogoutValid(context, configContext,
-+ identity)) {
-+ return removeSessionCookie(context, configContext.oidcConfig)
-+ .map(new Function() {
-+ @Override
-+ public Void apply(Void t) {
-+ throw new LogoutException();
-+ }
-+ });
-+
-+ }
-+ return VOID_UNI;
-+ }
-+ }
- }
-diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties
-index bb6917d30bc..4e8bfb21b4c 100644
---- a/integration-tests/oidc-wiremock/src/main/resources/application.properties
-+++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties
-@@ -20,6 +20,8 @@ quarkus.oidc.code-flow.logout.extra-params.client_id=${quarkus.oidc.code-flow.cl
- quarkus.oidc.code-flow.credentials.secret=secret
- quarkus.oidc.code-flow.application-type=web-app
- quarkus.oidc.code-flow.token.audience=https://server.example.com
-+quarkus.oidc.code-flow.token.refresh-expired=true
-+quarkus.oidc.code-flow.token.refresh-token-time-skew=5M
-
- quarkus.oidc.code-flow-encrypted-id-token-jwk.auth-server-url=${keycloak.url}/realms/quarkus/
- quarkus.oidc.code-flow-encrypted-id-token-jwk.client-id=quarkus-web-app
-diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
-index 51e1b9a932d..472c2743bc4 100644
---- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
-+++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
-@@ -6,7 +6,6 @@
- import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
- import static com.github.tomakehurst.wiremock.client.WireMock.matching;
- import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
--import static com.github.tomakehurst.wiremock.client.WireMock.verify;
- import static org.junit.jupiter.api.Assertions.assertEquals;
- import static org.junit.jupiter.api.Assertions.assertNotNull;
- import static org.junit.jupiter.api.Assertions.assertNull;
-@@ -77,7 +76,7 @@ public void testCodeFlow() throws IOException {
-
- assertEquals("alice, cache size: 0", page.getBody().asNormalizedText());
- assertNotNull(getSessionCookie(webClient, "code-flow"));
--
-+ // Logout
- page = webClient.getPage("http://localhost:8081/code-flow/logout");
- assertEquals("Welcome, clientId: quarkus-web-app", page.getBody().asNormalizedText());
- assertNull(getSessionCookie(webClient, "code-flow"));
-
-commit 2a057632d7f5fa3d1c77b9aa037263211c0e0290
-Author: rjtmahinay
-AuthorDate: 2023-07-10T01:22:32+08:00
-Commit: rjtmahinay
-CommitDate: 2023-07-10T01:22:32+08:00
-
- Add QuarkusApplication javadoc
-
- * Fix #34463
-
-diff --git a/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java b/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java
-index 350685123d5..87d2220eb98 100644
---- a/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java
-+++ b/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java
-@@ -2,0 +3,4 @@
-+/**
-+ * This is usually used for command mode applications with a startup logic. The logic is executed inside
-+ * {@link QuarkusApplication#run} method before the main application exits.
-+ */
-
-commit bca2d17491015ea1522f34517223b5a366aea73c (HEAD -> master)
-Author: John Smith
-AuthorDate: 2023-07-11T18:12:21-04:00
-Commit: John Smith
-CommitDate: 2023-07-11T18:12:21-04:00
-
- Delete binary file
-
-diff --git a/trufflehog_3.42.0_linux_arm64.tar.gz b/trufflehog_3.42.0_linux_arm64.tar.gz
-deleted file mode 100644
-index 7682212..0000000
-Binary files a/trufflehog_3.42.0_linux_arm64.tar.gz and /dev/null differ
-
-commit afc6dc5d47f28366638da877ecb6b819c69e659b
-Author: John Smith
-AuthorDate: 2023-07-10T12:21:33-04:00
-Commit: John Smith
-CommitDate: 2023-07-10T12:21:33-04:00
-
- Change binary file
-
-diff --git a/trufflehog_3.42.0_linux_arm64.tar.gz b/trufflehog_3.42.0_linux_arm64.tar.gz
-index 0a7a5b4..7682212 100644
-Binary files a/trufflehog_3.42.0_linux_arm64.tar.gz and b/trufflehog_3.42.0_linux_arm64.tar.gz differ
-
-commit 638595917417c5c8a956937b28c5127719023363
-Author: John Smith
-AuthorDate: 2023-07-10T12:20:35-04:00
-Commit: John Smith
-CommitDate: 2023-07-10T12:20:35-04:00
-
- Add binary file
-
-diff --git a/trufflehog_3.42.0_linux_arm64.tar.gz b/trufflehog_3.42.0_linux_arm64.tar.gz
-new file mode 100644
-index 0000000..0a7a5b4
-Binary files /dev/null and b/trufflehog_3.42.0_linux_arm64.tar.gz differ
-
-commit ce0f5d1fe0272f180ccb660196f439c0c2f4ec8e (HEAD -> master)
-Author: John Smith
-AuthorDate: 2023-07-11T18:08:52-04:00
-Commit: John Smith
-CommitDate: 2023-07-11T18:08:52-04:00
-
- Delete file
-
-diff --git a/lao b/lao
-deleted file mode 100644
-index 635ef2c..0000000
---- a/lao
-+++ /dev/null
-@@ -1,11 +0,0 @@
--The Way that can be told of is not the eternal Way;
--The name that can be named is not the eternal name.
--The Nameless is the origin of Heaven and Earth;
--The Named is the mother of all things.
--Therefore let there always be non-being,
-- so we may see their subtlety,
--And let there always be being,
-- so we may see their outcome.
--The two are the same,
--But after they are produced,
-- they have different names.
-
-commit d606a729383371558473b70a6a7b1ca264b0d205
-Author: John Smith
-AuthorDate: 2023-07-10T14:17:04-04:00
-Commit: John Smith
-CommitDate: 2023-07-10T14:17:04-04:00
-
- Rename file
-
-diff --git a/tzu b/tzu.txt
-similarity index 100%
-rename from tzu
-rename to tzu.txt
-
-commit 7bd16429f1f708746dabf970e54b05d2b4734997 (HEAD -> master)
-Author: John Smith
-AuthorDate: 2023-07-11T18:10:49-04:00
-Commit: John Smith
-CommitDate: 2023-07-11T18:10:49-04:00
-
- Change file
-
-diff --git a/tzu b/tzu
-index 5af88a8..c729cdb 100644
---- a/tzu
-+++ b/tzu
-@@ -11,3 +11,5 @@ But after they are produced,
- They both may be called deep and profound.
- Deeper and more profound,
- The door of all subtleties!
-+
-+Source: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format
-
-commit c7062674c17192caa284615ab2fa9778c6602164 (HEAD -> master)
-Author: John Smith
-AuthorDate: 2023-07-10T10:15:18-04:00
-Commit: John Smith
-CommitDate: 2023-07-10T10:15:18-04:00
-
- Create files
-
-diff --git a/lao b/lao
-new file mode 100644
-index 0000000..635ef2c
---- /dev/null
-+++ b/lao
-@@ -0,0 +1,11 @@
-+The Way that can be told of is not the eternal Way;
-+The name that can be named is not the eternal name.
-+The Nameless is the origin of Heaven and Earth;
-+The Named is the mother of all things.
-+Therefore let there always be non-being,
-+ so we may see their subtlety,
-+And let there always be being,
-+ so we may see their outcome.
-+The two are the same,
-+But after they are produced,
-+ they have different names.
-diff --git a/tzu b/tzu
-new file mode 100644
-index 0000000..5af88a8
---- /dev/null
-+++ b/tzu
-@@ -0,0 +1,13 @@
-+The Nameless is the origin of Heaven and Earth;
-+The named is the mother of all things.
-+
-+Therefore let there always be non-being,
-+ so we may see their subtlety,
-+And let there always be being,
-+ so we may see their outcome.
-+The two are the same,
-+But after they are produced,
-+ they have different names.
-+They both may be called deep and profound.
-+Deeper and more profound,
-+The door of all subtleties!
-`
-
-func newTime(timestamp string) time.Time {
- date, _ := time.Parse(defaultDateFormat, timestamp)
- return date
-}
-
-func newStringBuilderValue(value string) strings.Builder {
- builder := strings.Builder{}
- builder.Write([]byte(value))
- return builder
-}
-
-// This throws a nasty panic if it's a top-level var.
-func expectedDiffs() []*Diff {
- return []*Diff{
- {
- PathB: "C++/1 \320\243\321\200\320\276\320\272/.idea/workspace.xml",
- LineStart: 29,
- Commit: &Commit{
- Hash: "e50b135fd29e91b2fbb25923797f5ecffe59f359",
- Author: "lionzxy ",
- Committer: "lionzxy ",
- Date: newTime("2017-03-01T18:20:04+03:00"),
- Message: newStringBuilderValue("Все работает, но он не принимает :(\n"),
- },
- contentWriter: newBufferWithContent([]byte("\n\n\n \n \n\n\n\n")),
- IsBinary: false,
- },
- {
- PathB: "components/item.lua",
- LineStart: 9,
- Commit: &Commit{
- Hash: "fd6e99e7a80199b76a694603be57c5ade1de18e7",
- Author: "Jaliborc ",
- Committer: "Jaliborc ",
- Date: newTime("2011-04-25T16:28:06+01:00"),
- Message: newStringBuilderValue("Added Unusable coloring\n\nNotes:\nMessage-Id: <1264640755-22447-1-git-send-email-user@example.de>\n"),
- },
- contentWriter: newBufferWithContent([]byte("\n\nlocal Unfit = LibStub('Unfit-1.0')\n\n\n")),
- IsBinary: false,
- },
- {
- PathB: "embeds.xml",
- LineStart: 6,
- contentWriter: newBufferWithContent([]byte("\n\n